Overview
Some of the materials originate in the Hemberg
group course material with some of the text copied with a few edits.
Also see the OSCA book’s “Basic”
and “Advanced”
chapters on clustering. In particular, please read the overview
with regard to the comments on the “correctness” of any given clustering
result.
Once we have normalized the data and removed confounders we can carry
out analyses that are relevant to the biological questions at hand. The
exact nature of the analysis depends on the data set. One of the most
promising applications of scRNA-seq is de novo discovery and
annotation of cell-types based on transcription profiles. This requires
the identification of groups of cells based on the similarities of the
transcriptomes without any prior knowledge of the labels, or
unsupervised clustering. To avoid the challenges caused by the noise and
high dimensionality of the scRNA-seq data, clustering is performed after
feature selection and dimensionality reduction, for data that has not
required batch correction this would usually on the PCA output. As our
data has required batch correction we will use the “corrected”
reducedDims data.
We will focus graph-based clustering, however, it is also possible to
apply hierarchical clustering and k-means clustering on smaller data
sets - see the OSCA
book for details. Graph-base clustering is a more recent development
and better suited for scRNA-seq, especially large data sets.
Load data
We will use the data set generated in the previous session. This
contains 7 samples from the Caron data set. For the purposes of these
materials, in the interests of time, each sample has been downsampled to
only contain 500 cells.
sce <- readRDS("Robjects/DataIntegration_mnn.out.Rds")
Graph-based clustering overview
Graph-based clustering entails building a nearest-neighbour (NN)
graph using cells as nodes and their similarity as edges, then
identifying ‘communities’ of cells within the network. A graph-based
clustering method has three key parameters:
- How many neighbors are considered when constructing the graph
- What scheme is used to weight the edges
- Which community detection algorithm is used to define the
clusters
Connecting nodes (cells) based on nearest neighbours
Two types of NN graph may be use “K nearest-neighbour” (KNN) and
“shared nearest-neighbour” (SNN). In an KNN graph two nodes (cells), say
A and B, are connected by an edge if the distance between them is
amongst the k smallest distances from A to other cells. In an
SNN graph A and B are connected if the distance is amongst the
k samllest distances from A to other cells and also among the
k smallest distance from B to other cells.

In the figure above, if k is 5, then A and B would be
connected in a KNN graph as B is one of the 5 closest cells to A,
however, they would not be connected in an SNN graph as B has 5 other
cells that are closer to it than A.
The value of k can be roughly interpreted as the anticipated
size of the smallest subpopulation” (see scran’s
buildSNNGraph() manual).
The plot below shows the same data set as a network built using three
different numbers of neighbours: 5, 15 and 25 (from here).

Weighting the edges
The edges between nodes (cells) can be weighted based on the
similarity of the cells; edges connecting cells that are more closely
related will have a higher weight. The three common methods for this
weighting are (see the
bluster package documentation for the makeSNNGraph
function):
- rank - the weight is based on the highest rank of
the shared nearest neighbours
- number - the weight is based the number of nearest
neighbours in common between the two cells
- jaccard - the Jaccard index of
the two cells’ sets of nearest neighbours.
Grouping nodes (cells) into clusters
Clusters are identified using an algorithm that interprets the
connections of the graph to find groups of highly interconnected cells.
A variety of different algorithms are available to do this, in these
materials we will focus on three methods: walktrap, louvain and leiden.
See the OSCA
book for details of others available in scran.
Pros and Cons of graph based clustering
- Pros:
- fast and memory efficient (avoids the need to construct a distance
matrix for all pairs of cells)
- no assumptions on the shape of the clusters or the distribution of
cells within each cluster
- no need to specify a number of clusters to identify (but the size of
the neighbourhood used affects the size of clusters)
- Cons:
- loss of information beyond neighboring cells, which can affect
community detection in regions with many cells.
Implementation
The implementation of clustering in R is carried out using functions
from a number of different packages, in particular the bluster
and igraph packages. scran provides a handy “wrapper”
function clusterCells that allows us use a variety of
different algorithms with one simple command.
By default clusterCells just returns a vector containing
the cluster number for each cell. We can also retrieve the intermediate
statistics (varying according to the algorithm used) and the SNN graph
by specifying the bluster argument full = TRUE. If
you are only interested in retrieving the clusters, this isn’t necessary
but in this first instance we will retrieve the graph and visualise
it.
clustering1 <- clusterCells(sce, use.dimred="corrected", full=TRUE)
This has defined 14 clusters with varying numbers of cells:
table(clustering1$clusters)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
211 495 236 665 46 56 1112 85 16 76 63 51 21 39 11 175 142
The number of cells in the data set is large and plotting all the
cells would take too long, so we randomly choose 1000 nodes (cells) in
the network before plotting the resulting smaller network. Adding sample
data to the graph and plotting the results are done using the igraph
package. Cells can be color-coded by sample type:
# extract the graph
snn.gr <- clustering1$objects$graph
# Add Sample group to vertices (nodes, ie cells)
V(snn.gr)$SampleGroup <- as.character(colData(sce)$SampleGroup)
# pick 1000 nodes randomly
set.seed(1423)
selectedNodes <- sample(3500, 1000)
# subset graph for these 1000 randomly chosen nodes
snn.gr.subset <- subgraph(snn.gr, selectedNodes)
# set colors for clusters
grps <- V(snn.gr.subset)$SampleGroup
cols <- c("dodgerblue", "lightyellow")[as.numeric(factor(grps))]
names(cols) <- grps
# plot graph
plot.igraph(snn.gr.subset,
layout = layout_with_fr(snn.gr.subset),
vertex.size = 3,
vertex.label = NA,
vertex.color = cols,
frame.color = cols,
main = "default parameters"
)
# add legend
legend('bottomright',
legend=unique(names(cols)),
pch=21,
pt.bg=unique(cols),
pt.cex=1, cex=.6, bty="n", ncol=1)

More commonly we will visualise the clusters by superimposing them on
a tSNE or UMAP plot. We can store the clusters in the sce
object colData.
sce$Clusters1 <- clustering1$clusters
plotTSNE(sce, colour_by="Clusters1", text_by = "Clusters1")

Modularity
Several methods to detect clusters (‘communities’) in networks rely
on a metric called “modularity”. For a given partition of cells into
clusters, modularity measures how separated clusters are from each
other, based on the difference between the observed and expected weight
of edges between nodes. For the whole graph, the closer to 1 the
better.
The Walktrap method
The walktrap method relies on short random walks (a few steps)
through the network. These walks tend to be ‘trapped’ in
highly-connected regions of the network. Node similarity is measured
based on these walks. Nodes are first each assigned their own community.
Pairwise distances are computed and the two closest communities are
grouped. These steps are repeated a given number of times to produce a
dendrogram. Hierarchical clustering is then applied to the distance
matrix. The best partition is that with the highest modularity. The
original article describing the algorithm is Pons P, Latapy M (2006)
Computing communities in large networks using random walks. J Graph
Algorithms Appl 10(2):191–218
Walktrap is the default algorithm for clusterCells,
k is set to 10 by default and the default edge weighting is
“rank”. To explicitly request a specific algorithm and to set the
k to a different number of nearest neighbours, we use a
SNNGraphParam object from the bluster package
(which is the package clusterCells is using under the
hood).
Let’s set the k to 15 but keep the other parameters the
same. This time we will just return the clusters:
sce$walktrap15 <- clusterCells(sce,
use.dimred = "corrected",
BLUSPARAM = SNNGraphParam(k = 15, cluster.fun = "walktrap"))
This time we have defined 12 clustering. As a general rule,
increasing k will tend to decrease the number of clusters (not
always, but generally).
table(sce$walktrap15)
1 2 3 4 5 6 7 8 9 10 11 12 13
207 425 93 263 172 157 1142 69 69 88 182 38 595
We can visualise the assignment of cells from different samples to
the clusters using a heatmap.
table(sce$walktrap15, sce$SampleName) %>%
pheatmap(cluster_rows = FALSE, cluster_cols = FALSE)

Most clusters comprise cells from several replicates of a same sample
type, cluster 9 appears to be predominantly cells from the ETV6-RUNX
samples.
We can visualise this on the TSNE:
plotTSNE(sce, colour_by="walktrap15", text_by = "walktrap15")

plotTSNE(sce, colour_by="walktrap15") +
facet_wrap(sce$SampleGroup)

Additional clustering parameters (Advanced)
The different clustering algorithms may have additional parameters,
specific to the algorithm, that can be adjusted. With the walktrap
algorithm we could also tweak the number of “steps” in each walk. The
default is 4, but we could, for example, change this to 10 by adding the
parameter cluster.args = list(steps = 10) to the
SNNGraphParam object in the clusterCells
command.
The Louvain method
With the Louvain method, nodes are also first assigned their own
community. This hierarchical agglomerative method then progresses in
two-step iterations:
- nodes are re-assigned one at a time to the community for which they
increase modularity the most, if at all.
- a new, ‘aggregate’ network is built where nodes are the communities
formed in the previous step.
These two steps are repeated until modularity stops increasing. The
diagram below is copied from this
article.

We now apply the Louvain approach, store its outcome in the SCE
object and show cluster sizes.
sce$louvain15 <- clusterCells(sce,
use.dimred = "corrected",
BLUSPARAM = SNNGraphParam(k = 15, cluster.fun = "louvain"))
table(sce$louvain15)
1 2 3 4 5 6 7 8 9
496 601 432 645 605 146 191 275 109
The t-SNE plot shows cells color-coded by cluster membership:
plotTSNE(sce, colour_by="louvain15", text_by = "louvain15")

If we split by sample type we can see differences in the clusters
between the sample groups:
plotTSNE(sce, colour_by="louvain15") +
facet_wrap(~ sce$SampleGroup)

The Leiden method
The Leiden method improves on the Louvain method by guaranteeing that
at each iteration clusters are connected and well-separated. The method
includes an extra step in the iterations: after nodes are moved (step
1), the resulting partition is refined (step2) and only then the new
aggregate network made, and refined (step 3). The diagram below is
copied from this
article.

sce$leiden20 <- clusterCells(sce,
use.dimred = "corrected",
BLUSPARAM = SNNGraphParam(k = 20, cluster.fun = "leiden"))
table(sce$leiden20)
1 2 3 4 5 6 7 8 9 10 11 12
474 1095 263 617 135 170 188 2 272 73 170 41
The t-SNE plot shows cells color-coded by cluster membership:
plotTSNE(sce, colour_by="leiden20", text_by = "leiden20")

Assessing cluster behaviour
A variety of metrics are available to aid us in assessing the
behaviour of a particular clustering method on our data. These can help
us in assessing how well defined different clusters within a single
clustering are in terms of the relatedness of cells within the cluster
and the how well separated that cluster is from cells in other clusters,
and to compare the results of different clustering methods or parameter
values (e.g. different values for k).
We will consider “Silhouette width” and “Modularity”. Further details
and other metrics are described in the “Advanced”
section of the OSCA book.
Silhouette width
The silhouette width (so named after the look of the traditional
graph for plotting the results) is a measure of how closely related
cells within cluster are to one another versus how closely related cells
in the cluster are to cells in other clusters. This allows us to assess
cluster separation.
For each cell in the cluster we calculate the the average distance to
all other cells in the cluster and the average distance to all cells not
in the cluster. The cells silhouette width is the difference between
these divided by the maximum of the two values. Cells with a large
silhouette are strongly related to cells in the cluster, cells with a
negative silhouette width are more closely related to other
clusters.
We will use the approxSilhouette function from the
bluster package. The resulting table gives us the silhouette
width for each cell, the cluster it belongs to, and which other cluster
it is most closely related to.
sil.approx <- approxSilhouette(reducedDim(sce, "corrected"), clusters=sce$louvain15)
sil.approx
DataFrame with 3500 rows and 3 columns
cluster other width
<factor> <factor> <numeric>
1_AAACGGGCAGTTCATG-1 1 2 0.142069
1_AAACGGGGTTCACCTC-1 1 2 0.276745
1_AAAGATGAGCGATGAC-1 2 4 0.172973
1_AAAGATGCAGCCAATT-1 3 2 -0.405175
1_AAAGTAGCAATGCCAT-1 3 2 -0.312292
... ... ... ...
12_TTTCCTCGTCCAAGTT-1 5 6 0.3766600
12_TTTGCGCCAGGCTCAC-1 4 2 0.0906579
12_TTTGGTTTCCAAGCCG-1 7 2 0.1518246
12_TTTGTCACAATGAAAC-1 5 6 0.4611726
12_TTTGTCATCAGTTGAC-1 6 5 -0.0451985
We can view the results in as a beeswarm plot. We colour each cell
according to either its current cluster or, if the cell has a negative
silhouette width, the cluster that it is closest to.
silPlot.dat <- sil.approx %>%
as.data.frame() %>%
mutate(closestCluster = ifelse(width > 0, cluster, other) %>% factor())
ggplot(silPlot.dat, aes(x=cluster, y=width, colour=closestCluster)) +
ggbeeswarm::geom_quasirandom(method="smiley")

We could also look at the correspondence between different clusters
by plotting these numbers on a grid showing for each cluster number of
cells in that cluster that are closer to another cluster, colouring each
tile by the proportion of the total cells in the cluster that it
contains. Ideally we would like to see a strong diagonal band and only a
few off-diagonal tiles containing small number of cells.
plotSilGrid <- function(dat){
dat %>%
count(cluster, closestCluster, name="olap") %>%
group_by(cluster) %>%
mutate(total = sum(olap)) %>%
mutate(proportion = olap / total) %>%
mutate(proportion = ifelse(cluster == closestCluster, proportion, -proportion)) %>%
ggplot(aes(x = cluster, y = closestCluster)) +
geom_tile(aes(fill = proportion)) +
geom_text(aes(label = olap), size=5) +
scale_fill_gradientn(colors = c("#fc8d59", "#ffffbf", "#91cf60"),
limits = c(-1, 1)) +
geom_vline(xintercept=seq(0.5, 30.5, by=1)) +
geom_hline(yintercept=seq(0.5, 30.5, by=1), colour="lightgrey", linetype=2) +
guides(fill = "none") +
theme(
aspect.ratio = 1,
panel.background = element_blank())
}
plotSilGrid(silPlot.dat)

From these two plots we can see that clusters 2, 5 and 8 appear to
have a good degree of separation. In contrast, clusters 1, 2 and 4 have
a large number of cells that appear to be closer to cluster 3. It is
possible clusters 1-4 contain a degree of heterogeneity that suggests
further splitting of these clusters may be required - perhaps there
should more than 9 clusters to adequately reflect the biology.
Let’s do the same plots with the walktrap clusters generated with
k=15.
sil.approx <- approxSilhouette(reducedDim(sce, "corrected"), clusters=sce$walktrap15)
silPlot.dat <- sil.approx %>%
as.data.frame() %>%
mutate(closestCluster = ifelse(width > 0, cluster, other) %>% factor())
ggplot(silPlot.dat, aes(x=cluster, y=width, colour=closestCluster)) +
ggbeeswarm::geom_quasirandom(method="smiley")

plotSilGrid(silPlot.dat)

This clustering appears to have generated a set of clusters with
better separatedness than the Louvain method with a k of
15.
And again with the leiden clusters
sil.approx <- approxSilhouette(reducedDim(sce, "corrected"), clusters=sce$leiden20)
silPlot.dat <- sil.approx %>%
as.data.frame() %>%
mutate(closestCluster = ifelse(width > 0, cluster, other) %>% factor())
ggplot(silPlot.dat, aes(x=cluster, y=width, colour=closestCluster)) +
ggbeeswarm::geom_quasirandom(method="smiley")

plotSilGrid(silPlot.dat)

This seems similar to the walktrap clustering.
Modularity to assess clusters quality
As mentioned early, the modularity metric is used in evaluating the
separatedness between clusters. Some of the clustering algorithms,
e.g. Louvain, seek to optimise this for the entire NN graph as part of
their cluster detection. Modularity is a ratio between the observed
weights of the edges within a cluster versus the expected weights if the
edges were randomly distributed between all nodes. Rather than
calculating a single modularity value for the whole graph, we can
instead calculate a pair-wise modularity value between each pair of
clusters using the pairwiseModularity function from the
bluster package. For this we need to have the graph from the
clustering, so we will rerun the walktrap clustering with k=15 to obtain
this. We can plot the resulting ratios on a heatmap. We would expect the
highest modularity values to be on the diagonal.
walktrap15 <- clusterCells(sce,
use.dimred = "corrected",
BLUSPARAM = SNNGraphParam(k = 15, cluster.fun = "walktrap"),
full = TRUE)
g <- walktrap15$objects$graph
ratio <- pairwiseModularity(g, walktrap15$clusters, as.ratio=TRUE)
pheatmap(log2(ratio+1), cluster_rows=FALSE, cluster_cols=FALSE,
color=colorRampPalette(c("white", "blue"))(100))

Largely, this reflects what we saw from the silhouette widths, but
also reveals some additional inter-connectedness between other clusters
e.g. cluster 12 and cluster 8. We can also visualise this as network
graph where nodes are clusters and the edge weights are the
modularity.
cluster.gr <- igraph::graph_from_adjacency_matrix(log2(ratio+1),
mode="upper",
weighted=TRUE, diag=FALSE)
set.seed(11001010)
plot(cluster.gr,
edge.width=igraph::E(cluster.gr)$weight*5,
layout=igraph::layout_with_lgl)

Comparing two sets of clusters
The wide variety of clustering algorithms available and their
differing outputs reflect the different ways that it is possible to view
out data in high-dimensional space. It may often be useful to assess the
concordance between different clustering methods in order to assess how
clusters relate to each other - e.g. does one cluster from one method
equate to just one cluster in the other or is it a combination of
different clusters. This may be revealing about the underlying biology.
We will use the Jaccard index as measure of concordance between
clusters. A value of 1 represents perfect concordance between clusters
(i.e. they contain exactly the same cells).
jacc.mat <- linkClustersMatrix(sce$louvain15, sce$walktrap15)
rownames(jacc.mat) <- paste("Louvain", rownames(jacc.mat))
colnames(jacc.mat) <- paste("Walktrap", colnames(jacc.mat))
pheatmap(jacc.mat, color=viridis::viridis(100), cluster_cols=FALSE, cluster_rows=FALSE)

We can see that Louvain clusters 1, 5, 6 and 7 are equivalent to
walktrap clusters 2, 13, 6, and 11. The remaining Louvain clusters are
combinations of cells from various walktrap clusters. We may want to
look at marker genes for these clusters to assess what these two
different views are telling us about the biology.
Cluster sweep
As we have seen, there are a number of different parameters we can
change to alter the final clustering result - primarily the k
used to build the NN graph, the edge weighting method and the clustering
algorithm. There is no one gold standard that will fit all data, so, in
most cases, it is necessary to assess a number of different clusterings
to obtain one that provides a view of the data that suits our biological
interpretations. The clusterSweep function allows us to
apply a range of different parameters in one go and obtain the
clustering for each.
For example, suppose we wish to assess the effect of different values
of k on the walktrap clustering. We can parallelize this
process to make it faster.
out <- clusterSweep(reducedDim(sce, "corrected"),
BLUSPARAM = NNGraphParam(),
k = as.integer(c(5, 10, 15, 20, 25)),
cluster.fun = "walktrap",
BPPARAM=BiocParallel::MulticoreParam(5))
The resulting object is list containing a DataFrame with the clusters
for each combination of the clustering parameters and a corresponding
DataFrame showing the parameters used to generate each of these:
out$clusters[,1:4]
DataFrame with 3500 rows and 4 columns
k.5_cluster.fun.walktrap k.10_cluster.fun.walktrap k.15_cluster.fun.walktrap
<factor> <factor> <factor>
1 4 2 2
2 4 2 2
3 10 7 7
4 11 7 4
5 10 7 4
... ... ... ...
3496 16 4 13
3497 17 17 5
3498 12 16 11
3499 16 4 13
3500 9 4 6
k.20_cluster.fun.walktrap
<factor>
1 2
2 2
3 5
4 4
5 5
... ...
3496 13
3497 12
3498 10
3499 13
3500 6
out$parameters
DataFrame with 5 rows and 2 columns
k cluster.fun
<integer> <character>
k.5_cluster.fun.walktrap 5 walktrap
k.10_cluster.fun.walktrap 10 walktrap
k.15_cluster.fun.walktrap 15 walktrap
k.20_cluster.fun.walktrap 20 walktrap
k.25_cluster.fun.walktrap 25 walktrap
We can then combine this cluster sweep with the metrics for assessing
cluster behaviour in order to get a overview of the effects of these
parameter changes that may enable us to make some decisions as to which
clustering or clusterings we may wish to investigate further.
Here we will just look at the mean silhouette width and the number of
clusters.
df <- as.data.frame(out$parameters)
# get the number of clusters
df$num.clusters <- apply(out$clusters, 2, max)
# get the mean silhouette width
getMeanSil <- function(cluster) {
sil <- approxSilhouette(reducedDim(sce, "corrected"), cluster)
mean(sil$width)
}
df$silhouette <- map_dbl(as.list(out$clusters), getMeanSil)
nclPlot <- ggplot(df, aes(x = k, y = num.clusters)) +
geom_line(lwd=2)
silPlot <- ggplot(df, aes(x = k, y = silhouette)) +
geom_line(lwd=2)
nclPlot + silPlot

Based on our previous analysis and knowledge of the biology we may
feel that 13 clusters represents a good number clusters, but we can see
here that k = 15, k = 20 and k = 25 provide
this, but k = 25 gives us a better silhouette score. On the
other had, perhaps k = 10 provides greater resolution of cell
types, with more clusters with only a slight decrease in the silhouette
score.
Earlier we looked at the Jaccard index as a means of comparing two
different clusterings. We could apply the same method here:
jacc.mat <- linkClustersMatrix(out$clusters$k.10_cluster.fun.walktrap,
out$clusters$k.15_cluster.fun.walktrap)
rownames(jacc.mat) <- paste("Walktrap_10", rownames(jacc.mat))
colnames(jacc.mat) <- paste("Walktrap_15", colnames(jacc.mat))
pheatmap(jacc.mat, color=viridis::viridis(100), cluster_cols=FALSE, cluster_rows=FALSE)

Finally, we can add all (or a subset) of the clusterings from
clusterSweep to our SCE object.
colData(sce) <- cbind(colData(sce), DataFrame(out$clusters))
The OSCA
book provides some additional methods for comparing different
clusterings that can be combined with the cluster sweep results to
assess cluster behaviour under different parameters.
In this section, we have just done a sweep changing the k,
but it is also possible to combine this with multiple clustering
algorithms and multiple edge weightings.
Note: In practice, on a full data set, with multiple
algorithms and values of k, this can take a long time to run.
Usually I do not run this interactively in R studio, but instead write
an R script that will end by exporting the final output of
clusterSweep to an RDS object which I can later load into
R. This R script can then be sent as a job to the cluster. This is also
means the job can be massively more parallelized by using more cores. We
will see this in the exercise for this session.
Finalise clustering selection
When you have come to a decision about which clustering to use it is
convenient to add it to colData column called “label” using
the colLabels function. This means downstream code does not
need to be changed should you later decide to switch to a different
clustering (and makes the code easily re-usable for different
analyses).
For now we will use the walktrap k=25 clustering.
colLabels(sce) <- sce$k.25_cluster.fun.walktrap
Expression of known marker genes
If we expect our clusters to represent known cell types for which
there are well established marker genes, we can now start to investigate
the clusters by plotting in parallel the expression of these genes. This
can also help us in assessing if our clustering has satisfactorily
partitioned our cells.
plotTSNE(sce, colour_by = "label", text_by = "label") +
ggtitle("Walktrap clusters")

Having identified clusters, we now display the level of expression of
cell type marker genes to quickly match clusters with cell types. For
each marker we will plot its expression on a t-SNE, and show
distribution across each cluster on a violin plot.
We will be using gene symbols to identify the marker genes, so we
will switch the rownames in the SCE object to be gene symbols. We use
the scater function uniquifyFeatureNames to do this as
there are a few duplicated gene symbols.
rownames(sce) <- uniquifyFeatureNames(rowData(sce)$ID, rowData(sce)$Symbol)
B-cells markers
p1 <- plotTSNE(sce, by_exprs_values = "reconstructed",
colour_by = "MS4A1",
text_by = "label")
p2 <- plotTSNE(sce, by_exprs_values = "reconstructed"
, colour_by = "CD79A",
text_by = "label")
p1 + p2

plotExpression(sce,
exprs_values = "reconstructed",
x = "label",
colour_by = "label",
features=c("MS4A1", "CD79A"))

Monocyte markers
p1 <- plotTSNE(sce, by_exprs_values = "reconstructed",
colour_by = "FCGR3A",
text_by = "label")
p2 <- plotTSNE(sce, by_exprs_values = "reconstructed",
colour_by = "MS4A7",
text_by = "label")
p1 + p2

plotExpression(sce,
exprs_values = "reconstructed",
x = "label",
colour_by = "label",
features=c("FCGR3A", "MS4A7"))

Dendritic cell markers
p1 <- plotTSNE(sce, by_exprs_values = "reconstructed",
colour_by = "FCER1A",
text_by = "label")
p2 <- plotTSNE(sce, by_exprs_values = "reconstructed",
colour_by = "CST3",
text_by = "label")
p1 + p2

plotExpression(sce,
exprs_values = "reconstructed",
x = "label",
colour_by = "label",
features=c("FCER1A", "CST3"))

Save data
Write SCE object to file.
saveRDS(sce, file="Robjects/Caron_clustering_material.rds")
LS0tCnRpdGxlOiAiSW50cm9kdWN0aW9uIHRvIHNpbmdsZS1jZWxsIFJOQS1zZXEgYW5hbHlzaXMiCmF1dGhvcjogIkFzaGxleSBTYXdsZSwgU3RlcGhhbmUgQmFsbGVyZWF1IgpkYXRlOiAiTWF5IDIwMjIiCnN1YnRpdGxlOiBDbHVzdGVyaW5nCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogMgotLS0KCmBgYHtyIGtuaXRyX29wdGlvbnMsIGVjaG89RkFMU0UsIHJlc3VsdHM9ImhpZGUiLCBtZXNzYWdlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZXJyb3I9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPUZBTFNFKQpgYGAKCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQpsaWJyYXJ5KHNjYXRlcikgIyBzY1JuYVNlcSBRQwpsaWJyYXJ5KHNjcmFuKSAjIHNjUm5hU2VxIG5vcm1hbGlzYXRpb24KbGlicmFyeShibHVzdGVyKSAjIHNjUm5hU2VxIGNsdXN0ZXJpbmcKbGlicmFyeShjbHVzdGVyKSAjIGZvciBzaWxob3VldHRlCmxpYnJhcnkoaWdyYXBoKSAjIGZvciBncmFwaC1iYXNlZCBjbHVzdGVyaW5nIGFuZCBwbG90dGluZyBuZXR3b3JrcwpsaWJyYXJ5KHBoZWF0bWFwKSAjIGZvciBoZWF0bWFwIHBsb3R0aW5nCmxpYnJhcnkocGF0Y2h3b3JrKSAjIHRvIGNvbWJpbmUgcGxvdHMKbGlicmFyeSh0aWR5dmVyc2UpICMgZGF0YSB3cmFuZ2xpbmcgYW5kIHBsb3R0aW5nIChnZ3Bsb3QyKQpsaWJyYXJ5KERUKSAjIEZvciBuaWNlIHByaW50aW5nIG9mIHRhYmxlcyBpbiB0aGUgaHRtbApgYGAKCiMgT3ZlcnZpZXcKClNvbWUgb2YgdGhlIG1hdGVyaWFscyBvcmlnaW5hdGUgaW4gdGhlIFtIZW1iZXJnIGdyb3VwIGNvdXJzZQptYXRlcmlhbF0oaHR0cHM6Ly9iaW9jZWxsZ2VuLXB1YmxpYy5zdmkuZWR1LmF1L21pZ18yMDE5X3Njcm5hc2VxLXdvcmtzaG9wL2NsdXN0ZXJpbmctYW5kLWNlbGwtYW5ub3RhdGlvbi5odG1sKQp3aXRoIHNvbWUgb2YgdGhlIHRleHQgY29waWVkIHdpdGggYSBmZXcgZWRpdHMuIEFsc28gc2VlIHRoZSBPU0NBIGJvb2sncyBbIkJhc2ljIl0oaHR0cDovL2Jpb2NvbmR1Y3Rvci5vcmcvYm9va3MvcmVsZWFzZS9PU0NBLmJhc2ljL2NsdXN0ZXJpbmcuaHRtbCkgYW5kClsiQWR2YW5jZWQiXShodHRwOi8vYmlvY29uZHVjdG9yLm9yZy9ib29rcy9yZWxlYXNlL09TQ0EuYmFzaWMvY2x1c3RlcmluZy5odG1sKSAKY2hhcHRlcnMgb24gY2x1c3RlcmluZy4gSW4gcGFydGljdWxhciwgcGxlYXNlIHJlYWQgdGhlCltvdmVydmlld10oaHR0cDovL2Jpb2NvbmR1Y3Rvci5vcmcvYm9va3MvcmVsZWFzZS9PU0NBLmJhc2ljL2NsdXN0ZXJpbmcuaHRtbCNvdmVydmlldy0xKQp3aXRoIHJlZ2FyZCB0byB0aGUgY29tbWVudHMgb24gdGhlICJjb3JyZWN0bmVzcyIgb2YgYW55IGdpdmVuIGNsdXN0ZXJpbmcgcmVzdWx0LgoKT25jZSB3ZSBoYXZlIG5vcm1hbGl6ZWQgdGhlIGRhdGEgYW5kIHJlbW92ZWQgY29uZm91bmRlcnMgd2UgY2FuIGNhcnJ5IG91dAphbmFseXNlcyB0aGF0IGFyZSByZWxldmFudCB0byB0aGUgYmlvbG9naWNhbCBxdWVzdGlvbnMgYXQgaGFuZC4gVGhlIGV4YWN0IG5hdHVyZQpvZiB0aGUgYW5hbHlzaXMgZGVwZW5kcyBvbiB0aGUgZGF0YSBzZXQuIE9uZSBvZiB0aGUgbW9zdCBwcm9taXNpbmcgYXBwbGljYXRpb25zCm9mIHNjUk5BLXNlcSBpcyAqZGUgbm92byogZGlzY292ZXJ5IGFuZCBhbm5vdGF0aW9uIG9mIGNlbGwtdHlwZXMgYmFzZWQgb24KdHJhbnNjcmlwdGlvbiBwcm9maWxlcy4gVGhpcyByZXF1aXJlcyB0aGUgaWRlbnRpZmljYXRpb24gb2YgZ3JvdXBzIG9mIGNlbGxzCmJhc2VkIG9uIHRoZSBzaW1pbGFyaXRpZXMgb2YgdGhlIHRyYW5zY3JpcHRvbWVzIHdpdGhvdXQgYW55IHByaW9yIGtub3dsZWRnZSBvZgp0aGUgbGFiZWxzLCBvciB1bnN1cGVydmlzZWQgY2x1c3RlcmluZy4gVG8gYXZvaWQgdGhlIGNoYWxsZW5nZXMgY2F1c2VkIGJ5IHRoZQpub2lzZSBhbmQgaGlnaCBkaW1lbnNpb25hbGl0eSBvZiB0aGUgc2NSTkEtc2VxIGRhdGEsIGNsdXN0ZXJpbmcgaXMgcGVyZm9ybWVkCmFmdGVyIGZlYXR1cmUgc2VsZWN0aW9uIGFuZCBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24sIGZvciBkYXRhIHRoYXQgaGFzIG5vdCAKcmVxdWlyZWQgYmF0Y2ggY29ycmVjdGlvbiB0aGlzIHdvdWxkIHVzdWFsbHkgb24gdGhlIFBDQSBvdXRwdXQuIEFzIG91ciBkYXRhIGhhcwpyZXF1aXJlZCBiYXRjaCBjb3JyZWN0aW9uIHdlIHdpbGwgdXNlIHRoZSAiY29ycmVjdGVkIiByZWR1Y2VkRGltcyBkYXRhLgoKV2Ugd2lsbCBmb2N1cyBncmFwaC1iYXNlZCBjbHVzdGVyaW5nLCBob3dldmVyLCBpdCBpcyBhbHNvIHBvc3NpYmxlIHRvIGFwcGx5CmhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIGFuZCBrLW1lYW5zIGNsdXN0ZXJpbmcgb24gc21hbGxlciBkYXRhIHNldHMgLSBzZWUgdGhlCltPU0NBCmJvb2tdKGh0dHA6Ly9iaW9jb25kdWN0b3Iub3JnL2Jvb2tzL3JlbGVhc2UvT1NDQS5iYXNpYy9jbHVzdGVyaW5nLmh0bWwjdmVjdG9yLXF1YW50aXphdGlvbi13aXRoLWstbWVhbnMpCmZvciBkZXRhaWxzLiBHcmFwaC1iYXNlIGNsdXN0ZXJpbmcgaXMgYSBtb3JlIHJlY2VudCBkZXZlbG9wbWVudCBhbmQgYmV0dGVyCnN1aXRlZCBmb3Igc2NSTkEtc2VxLCBlc3BlY2lhbGx5IGxhcmdlIGRhdGEgc2V0cy4KCiMgTG9hZCBkYXRhCgpXZSB3aWxsIHVzZSB0aGUgZGF0YSBzZXQgZ2VuZXJhdGVkIGluIHRoZSBwcmV2aW91cyBzZXNzaW9uLiBUaGlzIGNvbnRhaW5zIDcKc2FtcGxlcyBmcm9tIHRoZSBDYXJvbiBkYXRhIHNldC4gRm9yIHRoZSBwdXJwb3NlcyBvZiB0aGVzZSBtYXRlcmlhbHMsIGluIHRoZQppbnRlcmVzdHMgb2YgdGltZSwgZWFjaCBzYW1wbGUgaGFzIGJlZW4gZG93bnNhbXBsZWQgdG8gb25seSBjb250YWluIDUwMCBjZWxscy4KCmBgYHtyIGxvYWRfZGF0YX0Kc2NlIDwtIHJlYWRSRFMoIlJvYmplY3RzL0RhdGFJbnRlZ3JhdGlvbl9tbm4ub3V0LlJkcyIpCmBgYAoKCmBgYHtyIGVjaG89RkFMU0V9CmNvbERhdGEoc2NlKSAlPiUgCiAgYXMuZGF0YS5mcmFtZSgpICU+JSAKICBzZWxlY3QoU2FtcGxlTmFtZSwgU2FtcGxlR3JvdXAsIERhdGFzZXROYW1lKSAlPiUgCiAgYWRkX2NvdW50KFNhbXBsZU5hbWUsIG5hbWU9Ik51bWJlciBvZiBDZWxscyIpICU+JSAKICBkaXN0aW5jdCgpICU+JSAKICBkYXRhdGFibGUob3B0aW9ucyA9IGxpc3QoZG9tPSJ0IiksIHJvd25hbWVzID0gRkFMU0UpCmBgYAoKIyBHcmFwaC1iYXNlZCBjbHVzdGVyaW5nIG92ZXJ2aWV3CgpHcmFwaC1iYXNlZCBjbHVzdGVyaW5nIGVudGFpbHMgYnVpbGRpbmcgYSBuZWFyZXN0LW5laWdoYm91ciAoTk4pIGdyYXBoIHVzaW5nCmNlbGxzIGFzIG5vZGVzIGFuZCB0aGVpciBzaW1pbGFyaXR5IGFzIGVkZ2VzLCB0aGVuIGlkZW50aWZ5aW5nICdjb21tdW5pdGllcycgb2YKY2VsbHMgd2l0aGluIHRoZSBuZXR3b3JrLiBBIGdyYXBoLWJhc2VkIGNsdXN0ZXJpbmcgbWV0aG9kIGhhcyB0aHJlZSBrZXkKcGFyYW1ldGVyczoKCiogSG93IG1hbnkgbmVpZ2hib3JzIGFyZSBjb25zaWRlcmVkIHdoZW4gY29uc3RydWN0aW5nIHRoZSBncmFwaCAgCiogV2hhdCBzY2hlbWUgaXMgdXNlZCB0byB3ZWlnaHQgdGhlIGVkZ2VzICAgCiogV2hpY2ggY29tbXVuaXR5IGRldGVjdGlvbiBhbGdvcml0aG0gaXMgdXNlZCB0byBkZWZpbmUgdGhlIGNsdXN0ZXJzCgojIyBDb25uZWN0aW5nIG5vZGVzIChjZWxscykgYmFzZWQgb24gbmVhcmVzdCBuZWlnaGJvdXJzCgpUd28gdHlwZXMgb2YgTk4gZ3JhcGggbWF5IGJlIHVzZSAiSyBuZWFyZXN0LW5laWdoYm91ciIgKEtOTikgYW5kICJzaGFyZWQKbmVhcmVzdC1uZWlnaGJvdXIiIChTTk4pLiBJbiBhbiBLTk4gZ3JhcGggdHdvIG5vZGVzIChjZWxscyksIHNheSBBIGFuZCBCLCBhcmUKY29ubmVjdGVkIGJ5IGFuIGVkZ2UgaWYgdGhlIGRpc3RhbmNlIGJldHdlZW4gdGhlbSBpcyBhbW9uZ3N0IHRoZSAqayogc21hbGxlc3QKZGlzdGFuY2VzIGZyb20gQSB0byBvdGhlciBjZWxscy4gSW4gYW4gU05OIGdyYXBoIEEgYW5kIEIgYXJlIGNvbm5lY3RlZCBpZiB0aGUKZGlzdGFuY2UgaXMgYW1vbmdzdCB0aGUgKmsqIHNhbWxsZXN0IGRpc3RhbmNlcyBmcm9tIEEgdG8gb3RoZXIgY2VsbHMgYW5kIGFsc28KYW1vbmcgdGhlICprKiBzbWFsbGVzdCBkaXN0YW5jZSBmcm9tIEIgdG8gb3RoZXIgY2VsbHMuCgohW10oSW1hZ2VzL0tOTnZTTk4uc3ZnKXt3aWR0aD00MCV9CgpJbiB0aGUgZmlndXJlIGFib3ZlLCBpZiAqayogaXMgNSwgdGhlbiBBIGFuZCBCIHdvdWxkIGJlIGNvbm5lY3RlZCBpbiBhIEtOTiBncmFwaAphcyBCIGlzIG9uZSBvZiB0aGUgNSBjbG9zZXN0IGNlbGxzIHRvIEEsIGhvd2V2ZXIsIHRoZXkgd291bGQgbm90IGJlIGNvbm5lY3RlZCBpbgphbiBTTk4gZ3JhcGggYXMgQiBoYXMgNSBvdGhlciBjZWxscyB0aGF0IGFyZSBjbG9zZXIgdG8gaXQgdGhhbiBBLgoKVGhlIHZhbHVlIG9mICprKiBjYW4gYmUgcm91Z2hseSBpbnRlcnByZXRlZCBhcyB0aGUgYW50aWNpcGF0ZWQgc2l6ZSBvZiB0aGUKc21hbGxlc3Qgc3VicG9wdWxhdGlvbiIgKHNlZSBbYHNjcmFuYCdzIGBidWlsZFNOTkdyYXBoKClgCm1hbnVhbF0oaHR0cHM6Ly9yZHJyLmlvL2Jpb2Mvc2NyYW4vbWFuL2J1aWxkU05OR3JhcGguaHRtbCkpLgoKVGhlIHBsb3QgYmVsb3cgc2hvd3MgdGhlIHNhbWUgZGF0YSBzZXQgYXMgYSBuZXR3b3JrIGJ1aWx0IHVzaW5nIHRocmVlIGRpZmZlcmVudApudW1iZXJzIG9mIG5laWdoYm91cnM6IDUsIDE1IGFuZCAyNSAoZnJvbQpbaGVyZV0oaHR0cHM6Ly9iaW9jZWxsZ2VuLXB1YmxpYy5zdmkuZWR1LmF1L21pZ18yMDE5X3Njcm5hc2VxLXdvcmtzaG9wL2NsdXN0ZXJpbmctYW5kLWNlbGwtYW5ub3RhdGlvbi5odG1sI2V4YW1wbGUtMS4tZ3JhcGgtYmFzZWQtY2x1c3RlcmluZy1kZW5nLWRhdGFzZXQpKS4KCiFbXShJbWFnZXMvYmlvQ2VsbEdlbkdyYXBoRGVuZy5wbmcpCgojIyBXZWlnaHRpbmcgdGhlIGVkZ2VzCgpUaGUgZWRnZXMgYmV0d2VlbiBub2RlcyAoY2VsbHMpIGNhbiBiZSB3ZWlnaHRlZCBiYXNlZCBvbiB0aGUgc2ltaWxhcml0eSBvZiB0aGUKY2VsbHM7IGVkZ2VzIGNvbm5lY3RpbmcgY2VsbHMgdGhhdCBhcmUgbW9yZSBjbG9zZWx5IHJlbGF0ZWQgd2lsbCBoYXZlIGEgaGlnaGVyCndlaWdodC4gVGhlIHRocmVlIGNvbW1vbiBtZXRob2RzIGZvciB0aGlzIHdlaWdodGluZyBhcmUgKHNlZSBbdGhlICpibHVzdGVyKiAKcGFja2FnZSBkb2N1bWVudGF0aW9uIGZvciB0aGUgYG1ha2VTTk5HcmFwaGAgZnVuY3Rpb25dKGh0dHBzOi8vcmRyci5pby9naXRodWIvTFRMQS9ibHVzdGVyL21hbi9tYWtlU05OR3JhcGguaHRtbCkpOgoKKiAqKnJhbmsqKiAtIHRoZSB3ZWlnaHQgaXMgYmFzZWQgb24gdGhlIGhpZ2hlc3QgcmFuayBvZiB0aGUgc2hhcmVkIG5lYXJlc3QKbmVpZ2hib3VycyAgCiogKipudW1iZXIqKiAtIHRoZSB3ZWlnaHQgaXMgYmFzZWQgdGhlIG51bWJlciBvZiBuZWFyZXN0IG5laWdoYm91cnMgaW4gY29tbW9uCmJldHdlZW4gdGhlIHR3byBjZWxscyAgCiogKipqYWNjYXJkKiogLSB0aGUgW0phY2NhcmQgaW5kZXhdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0phY2NhcmRfaW5kZXgpIApvZiB0aGUgdHdvIGNlbGxzJyBzZXRzIG9mIG5lYXJlc3QgbmVpZ2hib3Vycy4gIAoKIyMgR3JvdXBpbmcgbm9kZXMgKGNlbGxzKSBpbnRvIGNsdXN0ZXJzCgpDbHVzdGVycyBhcmUgaWRlbnRpZmllZCB1c2luZyBhbiBhbGdvcml0aG0gdGhhdCBpbnRlcnByZXRzIHRoZSBjb25uZWN0aW9ucwpvZiB0aGUgZ3JhcGggdG8gZmluZCBncm91cHMgb2YgaGlnaGx5IGludGVyY29ubmVjdGVkIGNlbGxzLiBBIHZhcmlldHkgb2YKZGlmZmVyZW50IGFsZ29yaXRobXMgYXJlIGF2YWlsYWJsZSB0byBkbyB0aGlzLCBpbiB0aGVzZSBtYXRlcmlhbHMgd2Ugd2lsbCBmb2N1cwpvbiB0aHJlZSBtZXRob2RzOiB3YWxrdHJhcCwgbG91dmFpbiBhbmQgbGVpZGVuLiBTZWUgdGhlIFtPU0NBCmJvb2tdKGh0dHA6Ly9iaW9jb25kdWN0b3Iub3JnL2Jvb2tzL3JlbGVhc2UvT1NDQS5iYXNpYy9jbHVzdGVyaW5nLmh0bWwjb3ZlcnZpZXctMSkKZm9yIGRldGFpbHMgb2Ygb3RoZXJzIGF2YWlsYWJsZSBpbiAqc2NyYW4qLgoKIyMgUHJvcyBhbmQgQ29ucyBvZiBncmFwaCBiYXNlZCBjbHVzdGVyaW5nCgoqIFByb3M6CiAgICArIGZhc3QgYW5kIG1lbW9yeSBlZmZpY2llbnQgKGF2b2lkcyB0aGUgbmVlZCB0byBjb25zdHJ1Y3QgYSBkaXN0YW5jZSBtYXRyaXgKICAgIGZvciBhbGwgcGFpcnMgb2YgY2VsbHMpCiAgICArIG5vIGFzc3VtcHRpb25zIG9uIHRoZSBzaGFwZSBvZiB0aGUgY2x1c3RlcnMgb3IgdGhlIGRpc3RyaWJ1dGlvbiBvZiBjZWxscwogICAgd2l0aGluIGVhY2ggY2x1c3RlcgogICAgKyBubyBuZWVkIHRvIHNwZWNpZnkgYSBudW1iZXIgb2YgY2x1c3RlcnMgdG8gaWRlbnRpZnkgKGJ1dCB0aGUgc2l6ZSBvZiB0aGUKICAgIG5laWdoYm91cmhvb2QgdXNlZCBhZmZlY3RzIHRoZSBzaXplIG9mIGNsdXN0ZXJzKQoqIENvbnM6CiAgICArIGxvc3Mgb2YgaW5mb3JtYXRpb24gYmV5b25kIG5laWdoYm9yaW5nIGNlbGxzLCB3aGljaCBjYW4gYWZmZWN0IGNvbW11bml0eSBkZXRlY3Rpb24gaW4gcmVnaW9ucyB3aXRoIG1hbnkgY2VsbHMuCgoKIyBJbXBsZW1lbnRhdGlvbgoKVGhlIGltcGxlbWVudGF0aW9uIG9mIGNsdXN0ZXJpbmcgaW4gUiBpcyBjYXJyaWVkIG91dCB1c2luZyBmdW5jdGlvbnMgZnJvbSBhCm51bWJlciBvZiBkaWZmZXJlbnQgcGFja2FnZXMsIGluIHBhcnRpY3VsYXIgdGhlICpibHVzdGVyKiBhbmQgKmlncmFwaCogcGFja2FnZXMuCipzY3JhbiogcHJvdmlkZXMgYSBoYW5keSAid3JhcHBlciIgZnVuY3Rpb24gYGNsdXN0ZXJDZWxsc2AgdGhhdCBhbGxvd3MgdXMgdXNlIGEKdmFyaWV0eSBvZiBkaWZmZXJlbnQgYWxnb3JpdGhtcyB3aXRoIG9uZSBzaW1wbGUgY29tbWFuZC4KCkJ5IGRlZmF1bHQgYGNsdXN0ZXJDZWxsc2AganVzdCByZXR1cm5zIGEgdmVjdG9yIGNvbnRhaW5pbmcgdGhlIGNsdXN0ZXIgbnVtYmVyCmZvciBlYWNoIGNlbGwuIFdlIGNhbiBhbHNvIHJldHJpZXZlIHRoZSBpbnRlcm1lZGlhdGUgc3RhdGlzdGljcyAodmFyeWluZwphY2NvcmRpbmcgdG8gdGhlIGFsZ29yaXRobSB1c2VkKSBhbmQgdGhlIFNOTiBncmFwaCBieSBzcGVjaWZ5aW5nIHRoZSAqYmx1c3RlcioKYXJndW1lbnQgYGZ1bGwgPSBUUlVFYC4gSWYgeW91IGFyZSBvbmx5IGludGVyZXN0ZWQgaW4gcmV0cmlldmluZyB0aGUgY2x1c3RlcnMsCnRoaXMgaXNuJ3QgbmVjZXNzYXJ5IGJ1dCBpbiB0aGlzIGZpcnN0IGluc3RhbmNlIHdlIHdpbGwgcmV0cmlldmUgdGhlIGdyYXBoIGFuZAp2aXN1YWxpc2UgaXQuCgpgYGB7ciBjb21wX3Nubn0KY2x1c3RlcmluZzEgPC0gY2x1c3RlckNlbGxzKHNjZSwgdXNlLmRpbXJlZD0iY29ycmVjdGVkIiwgZnVsbD1UUlVFKQpgYGAKClRoaXMgaGFzIGRlZmluZWQgMTQgY2x1c3RlcnMgd2l0aCB2YXJ5aW5nIG51bWJlcnMgb2YgY2VsbHM6CgpgYGB7cn0KdGFibGUoY2x1c3RlcmluZzEkY2x1c3RlcnMpCmBgYAoKVGhlIG51bWJlciBvZiBjZWxscyBpbiB0aGUgZGF0YSBzZXQgaXMgbGFyZ2UgYW5kIHBsb3R0aW5nIGFsbCB0aGUgY2VsbHMgd291bGQKdGFrZSB0b28gbG9uZywgc28gd2UgcmFuZG9tbHkgY2hvb3NlIDEwMDAgbm9kZXMgKGNlbGxzKSBpbiB0aGUgbmV0d29yayBiZWZvcmUKcGxvdHRpbmcgdGhlIHJlc3VsdGluZyBzbWFsbGVyIG5ldHdvcmsuIEFkZGluZyBzYW1wbGUgZGF0YSB0byB0aGUgZ3JhcGggYW5kCnBsb3R0aW5nIHRoZSByZXN1bHRzIGFyZSBkb25lIHVzaW5nIHRoZSBbKmlncmFwaCoKcGFja2FnZV0oaHR0cHM6Ly9pZ3JhcGgub3JnL3IvZG9jL3N1YmdyYXBoLmh0bWwpLiBDZWxscyBjYW4gYmUgY29sb3ItY29kZWQgYnkKc2FtcGxlIHR5cGU6CgpgYGB7ciBzaG93X3dhbGt0cmFwX2dyYXBoLCB3YXJuaW5nID0gRkFMU0UsIGZpZy5oZWlnaHQ9Niwgb3V0LndpZHRoID0gJzEwMCUnfQojIGV4dHJhY3QgdGhlIGdyYXBoCnNubi5nciA8LSBjbHVzdGVyaW5nMSRvYmplY3RzJGdyYXBoCgojIEFkZCBTYW1wbGUgZ3JvdXAgdG8gdmVydGljZXMgKG5vZGVzLCBpZSBjZWxscykKVihzbm4uZ3IpJFNhbXBsZUdyb3VwIDwtIGFzLmNoYXJhY3Rlcihjb2xEYXRhKHNjZSkkU2FtcGxlR3JvdXApCgojIHBpY2sgMTAwMCBub2RlcyByYW5kb21seQpzZXQuc2VlZCgxNDIzKQpzZWxlY3RlZE5vZGVzIDwtIHNhbXBsZSgzNTAwLCAxMDAwKQoKIyBzdWJzZXQgZ3JhcGggZm9yIHRoZXNlIDEwMDAgcmFuZG9tbHkgY2hvc2VuIG5vZGVzCnNubi5nci5zdWJzZXQgPC0gc3ViZ3JhcGgoc25uLmdyLCBzZWxlY3RlZE5vZGVzKQoKIyBzZXQgY29sb3JzIGZvciBjbHVzdGVycwpncnBzIDwtICBWKHNubi5nci5zdWJzZXQpJFNhbXBsZUdyb3VwCmNvbHMgPC0gYygiZG9kZ2VyYmx1ZSIsICJsaWdodHllbGxvdyIpW2FzLm51bWVyaWMoZmFjdG9yKGdycHMpKV0KbmFtZXMoY29scykgPC0gZ3JwcwoKIyBwbG90IGdyYXBoCnBsb3QuaWdyYXBoKHNubi5nci5zdWJzZXQsCiAgbGF5b3V0ID0gbGF5b3V0X3dpdGhfZnIoc25uLmdyLnN1YnNldCksCiAgdmVydGV4LnNpemUgPSAzLCAKICB2ZXJ0ZXgubGFiZWwgPSBOQSwKICB2ZXJ0ZXguY29sb3IgPSBjb2xzLAogIGZyYW1lLmNvbG9yID0gY29scywKICBtYWluID0gImRlZmF1bHQgcGFyYW1ldGVycyIKKQoKIyBhZGQgbGVnZW5kCmxlZ2VuZCgnYm90dG9tcmlnaHQnLAogICAgICAgbGVnZW5kPXVuaXF1ZShuYW1lcyhjb2xzKSksCiAgICAgICBwY2g9MjEsCiAgICAgICBwdC5iZz11bmlxdWUoY29scyksCiAgICAgICBwdC5jZXg9MSwgY2V4PS42LCBidHk9Im4iLCBuY29sPTEpCmBgYAoKTW9yZSBjb21tb25seSB3ZSB3aWxsIHZpc3VhbGlzZSB0aGUgY2x1c3RlcnMgYnkgc3VwZXJpbXBvc2luZyB0aGVtIG9uIGEgdFNORSBvcgpVTUFQIHBsb3QuIFdlIGNhbiBzdG9yZSB0aGUgY2x1c3RlcnMgaW4gdGhlIGBzY2VgIG9iamVjdCBgY29sRGF0YWAuCgpgYGB7cn0Kc2NlJENsdXN0ZXJzMSA8LSBjbHVzdGVyaW5nMSRjbHVzdGVycwpwbG90VFNORShzY2UsIGNvbG91cl9ieT0iQ2x1c3RlcnMxIiwgdGV4dF9ieSA9ICJDbHVzdGVyczEiKQpgYGAKCiMjIyBNb2R1bGFyaXR5CgpTZXZlcmFsIG1ldGhvZHMgdG8gZGV0ZWN0IGNsdXN0ZXJzICgnY29tbXVuaXRpZXMnKSBpbiBuZXR3b3JrcyByZWx5IG9uIGEgbWV0cmljCmNhbGxlZCAibW9kdWxhcml0eSIuIEZvciBhIGdpdmVuIHBhcnRpdGlvbiBvZiBjZWxscyBpbnRvIGNsdXN0ZXJzLCBtb2R1bGFyaXR5Cm1lYXN1cmVzIGhvdyBzZXBhcmF0ZWQgY2x1c3RlcnMgYXJlIGZyb20gZWFjaCBvdGhlciwgYmFzZWQgb24gdGhlIGRpZmZlcmVuY2UKYmV0d2VlbiB0aGUgb2JzZXJ2ZWQgYW5kIGV4cGVjdGVkIHdlaWdodCBvZiBlZGdlcyBiZXR3ZWVuIG5vZGVzLiBGb3IgdGhlIHdob2xlCmdyYXBoLCB0aGUgY2xvc2VyIHRvIDEgdGhlIGJldHRlci4KCiMjIyBUaGUgV2Fsa3RyYXAgbWV0aG9kCgpUaGUgd2Fsa3RyYXAgbWV0aG9kIHJlbGllcyBvbiBzaG9ydCByYW5kb20gd2Fsa3MgKGEgZmV3IHN0ZXBzKSB0aHJvdWdoIHRoZQpuZXR3b3JrLiBUaGVzZSB3YWxrcyB0ZW5kIHRvIGJlICd0cmFwcGVkJyBpbiBoaWdobHktY29ubmVjdGVkIHJlZ2lvbnMgb2YgdGhlCm5ldHdvcmsuIE5vZGUgc2ltaWxhcml0eSBpcyBtZWFzdXJlZCBiYXNlZCBvbiB0aGVzZSB3YWxrcy4gTm9kZXMgYXJlIGZpcnN0IGVhY2gKYXNzaWduZWQgdGhlaXIgb3duIGNvbW11bml0eS4gUGFpcndpc2UgZGlzdGFuY2VzIGFyZSBjb21wdXRlZCBhbmQgdGhlIHR3bwpjbG9zZXN0IGNvbW11bml0aWVzIGFyZSBncm91cGVkLiBUaGVzZSBzdGVwcyBhcmUgcmVwZWF0ZWQgYSBnaXZlbiBudW1iZXIgb2YKdGltZXMgdG8gcHJvZHVjZSBhIGRlbmRyb2dyYW0uIEhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIGlzIHRoZW4gYXBwbGllZCB0byB0aGUKZGlzdGFuY2UgbWF0cml4LiBUaGUgYmVzdCBwYXJ0aXRpb24gaXMgdGhhdCB3aXRoIHRoZSBoaWdoZXN0IG1vZHVsYXJpdHkuIFRoZQpvcmlnaW5hbCBhcnRpY2xlIGRlc2NyaWJpbmcgdGhlIGFsZ29yaXRobSBpcyBbUG9ucyBQLCBMYXRhcHkgTSAoMjAwNikgQ29tcHV0aW5nCmNvbW11bml0aWVzIGluIGxhcmdlIG5ldHdvcmtzIHVzaW5nIHJhbmRvbSB3YWxrcy4gSiBHcmFwaCBBbGdvcml0aG1zIEFwcGwKMTAoMik6MTkx4oCTMjE4XShodHRwOi8vYXJ4aXYub3JnL2Ficy9waHlzaWNzLzA1MTIxMDYpCgpXYWxrdHJhcCBpcyB0aGUgZGVmYXVsdCBhbGdvcml0aG0gZm9yIGBjbHVzdGVyQ2VsbHNgLCAqayogaXMgc2V0IHRvIDEwIGJ5CmRlZmF1bHQgYW5kIHRoZSBkZWZhdWx0IGVkZ2Ugd2VpZ2h0aW5nIGlzICJyYW5rIi4gVG8gZXhwbGljaXRseSByZXF1ZXN0IGEKc3BlY2lmaWMgYWxnb3JpdGhtIGFuZCB0byBzZXQgdGhlICprKiB0byBhIGRpZmZlcmVudCBudW1iZXIgb2YgbmVhcmVzdApuZWlnaGJvdXJzLCB3ZSB1c2UgYSBgU05OR3JhcGhQYXJhbWAgb2JqZWN0IGZyb20gdGhlICpibHVzdGVyKiBwYWNrYWdlICh3aGljaCBpcwp0aGUgcGFja2FnZSAqY2x1c3RlckNlbGxzKiBpcyB1c2luZyB1bmRlciB0aGUgaG9vZCkuCgpMZXQncyBzZXQgdGhlICprKiB0byAxNSBidXQga2VlcCB0aGUgb3RoZXIgcGFyYW1ldGVycyB0aGUgc2FtZS4gVGhpcyB0aW1lIHdlCndpbGwganVzdCByZXR1cm4gdGhlIGNsdXN0ZXJzOgoKYGBge3J9CnNjZSR3YWxrdHJhcDE1IDwtIGNsdXN0ZXJDZWxscyhzY2UsIAogICAgICAgICAgICAgICAgICAgICAgICAgICB1c2UuZGltcmVkID0gImNvcnJlY3RlZCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBCTFVTUEFSQU0gPSBTTk5HcmFwaFBhcmFtKGsgPSAxNSwgY2x1c3Rlci5mdW4gPSAid2Fsa3RyYXAiKSkKYGBgCgoKVGhpcyB0aW1lIHdlIGhhdmUgZGVmaW5lZCAxMiBjbHVzdGVyaW5nLiBBcyBhIGdlbmVyYWwgcnVsZSwgaW5jcmVhc2luZyAqayogd2lsbAp0ZW5kIHRvIGRlY3JlYXNlIHRoZSBudW1iZXIgb2YgY2x1c3RlcnMgKG5vdCBhbHdheXMsIGJ1dCBnZW5lcmFsbHkpLgoKYGBge3J9CnRhYmxlKHNjZSR3YWxrdHJhcDE1KQpgYGAKCldlIGNhbiB2aXN1YWxpc2UgdGhlIGFzc2lnbm1lbnQgb2YgY2VsbHMgZnJvbSBkaWZmZXJlbnQgc2FtcGxlcyB0byB0aGUgY2x1c3RlcnMKdXNpbmcgYSBoZWF0bWFwLgoKYGBge3IgcmVwX2NsdXN0ZXJzX3NhbXBsZXMsIGZpZy53aWR0aD00LCBmaWcuaGVpZ2h0PTV9CnRhYmxlKHNjZSR3YWxrdHJhcDE1LCBzY2UkU2FtcGxlTmFtZSkgJT4lIAogIHBoZWF0bWFwKGNsdXN0ZXJfcm93cyA9IEZBTFNFLCBjbHVzdGVyX2NvbHMgPSBGQUxTRSkKYGBgCgpNb3N0IGNsdXN0ZXJzIGNvbXByaXNlIGNlbGxzIGZyb20gc2V2ZXJhbCByZXBsaWNhdGVzIG9mIGEgc2FtZSBzYW1wbGUgdHlwZSwKY2x1c3RlciA5IGFwcGVhcnMgdG8gYmUgcHJlZG9taW5hbnRseSBjZWxscyBmcm9tIHRoZSBFVFY2LVJVTlggc2FtcGxlcy4KCldlIGNhbiB2aXN1YWxpc2UgdGhpcyBvbiB0aGUgVFNORToKCmBgYHtyfQpwbG90VFNORShzY2UsIGNvbG91cl9ieT0id2Fsa3RyYXAxNSIsIHRleHRfYnkgPSAid2Fsa3RyYXAxNSIpCmBgYAoKCmBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9Nn0KcGxvdFRTTkUoc2NlLCBjb2xvdXJfYnk9IndhbGt0cmFwMTUiKSArCiAgZmFjZXRfd3JhcChzY2UkU2FtcGxlR3JvdXApCmBgYAoKIyMgQWRkaXRpb25hbCBjbHVzdGVyaW5nIHBhcmFtZXRlcnMgKEFkdmFuY2VkKQoKVGhlIGRpZmZlcmVudCBjbHVzdGVyaW5nIGFsZ29yaXRobXMgbWF5IGhhdmUgYWRkaXRpb25hbCBwYXJhbWV0ZXJzLCBzcGVjaWZpYyB0bwp0aGUgYWxnb3JpdGhtLCB0aGF0IGNhbiBiZSBhZGp1c3RlZC4gV2l0aCB0aGUgd2Fsa3RyYXAgYWxnb3JpdGhtIHdlIGNvdWxkIGFsc28KdHdlYWsgdGhlIG51bWJlciBvZiAic3RlcHMiIGluIGVhY2ggd2Fsay4gVGhlIGRlZmF1bHQgaXMgNCwgYnV0IHdlIGNvdWxkLCBmb3IKZXhhbXBsZSwgY2hhbmdlIHRoaXMgdG8gMTAgYnkgYWRkaW5nIHRoZSBwYXJhbWV0ZXIgYGNsdXN0ZXIuYXJncyA9IGxpc3Qoc3RlcHMgPQoxMClgIHRvIHRoZSBgU05OR3JhcGhQYXJhbWAgb2JqZWN0IGluIHRoZSBgY2x1c3RlckNlbGxzYCBjb21tYW5kLgoKIyBUaGUgTG91dmFpbiBtZXRob2QKCldpdGggdGhlIExvdXZhaW4gbWV0aG9kLCBub2RlcyBhcmUgYWxzbyBmaXJzdCBhc3NpZ25lZCB0aGVpciBvd24gY29tbXVuaXR5LiBUaGlzCmhpZXJhcmNoaWNhbCBhZ2dsb21lcmF0aXZlIG1ldGhvZCB0aGVuIHByb2dyZXNzZXMgaW4gdHdvLXN0ZXAgaXRlcmF0aW9uczogCgoxLiBub2RlcyBhcmUgcmUtYXNzaWduZWQgb25lIGF0IGEgdGltZSB0byB0aGUgY29tbXVuaXR5IGZvciB3aGljaCB0aGV5IGluY3JlYXNlCm1vZHVsYXJpdHkgdGhlIG1vc3QsIGlmIGF0IGFsbC4gCjIuIGEgbmV3LCAnYWdncmVnYXRlJyBuZXR3b3JrIGlzIGJ1aWx0IHdoZXJlIG5vZGVzIGFyZSB0aGUgY29tbXVuaXRpZXMgZm9ybWVkIGluCnRoZSBwcmV2aW91cyBzdGVwLgoKVGhlc2UgdHdvIHN0ZXBzIGFyZSByZXBlYXRlZCB1bnRpbCBtb2R1bGFyaXR5IHN0b3BzIGluY3JlYXNpbmcuIFRoZSBkaWFncmFtCmJlbG93IGlzIGNvcGllZCBmcm9tIFt0aGlzCmFydGljbGVdKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvczQxNTk4LTAxOS00MTY5NS16I0ZpZzEpLgoKPGltZyBzcmM9IkltYWdlcy9sZWlkZW5fRmlnMV9IVE1MLnBuZyIgc3R5bGU9Im1hcmdpbjphdXRvOyBkaXNwbGF5OmJsb2NrIiAvPgoKV2Ugbm93IGFwcGx5IHRoZSBMb3V2YWluIGFwcHJvYWNoLCBzdG9yZSBpdHMgb3V0Y29tZSBpbiB0aGUgU0NFIG9iamVjdCBhbmQgc2hvdwpjbHVzdGVyIHNpemVzLgoKYGBge3IgY2x1c3Rlcl9sb3V2YWlufQpzY2UkbG91dmFpbjE1IDwtIGNsdXN0ZXJDZWxscyhzY2UsIAogICAgICAgICAgICAgICAgICAgICAgICAgICB1c2UuZGltcmVkID0gImNvcnJlY3RlZCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBCTFVTUEFSQU0gPSBTTk5HcmFwaFBhcmFtKGsgPSAxNSwgY2x1c3Rlci5mdW4gPSAibG91dmFpbiIpKQpgYGAKCgpgYGB7cn0KdGFibGUoc2NlJGxvdXZhaW4xNSkKYGBgCgpUaGUgdC1TTkUgcGxvdCBzaG93cyBjZWxscyBjb2xvci1jb2RlZCBieSBjbHVzdGVyIG1lbWJlcnNoaXA6CgpgYGB7ciBzaG93X2xvdXZhaW5fdHNuZV8xLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD04fQpwbG90VFNORShzY2UsIGNvbG91cl9ieT0ibG91dmFpbjE1IiwgdGV4dF9ieSA9ICJsb3V2YWluMTUiKQpgYGAKCklmIHdlIHNwbGl0IGJ5IHNhbXBsZSB0eXBlIHdlIGNhbiBzZWUgZGlmZmVyZW5jZXMgaW4gdGhlIGNsdXN0ZXJzIGJldHdlZW4gdGhlCnNhbXBsZSBncm91cHM6CgpgYGB7ciBzaG93X2xvdXZhaW5fdHNuZV8yLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9Nn0KcGxvdFRTTkUoc2NlLCBjb2xvdXJfYnk9ImxvdXZhaW4xNSIpICsgCiAgZmFjZXRfd3JhcCh+IHNjZSRTYW1wbGVHcm91cCkKYGBgCgojIyMjIFRoZSBMZWlkZW4gbWV0aG9kCgpUaGUgTGVpZGVuIG1ldGhvZCBpbXByb3ZlcyBvbiB0aGUgTG91dmFpbiBtZXRob2QgYnkgZ3VhcmFudGVlaW5nIHRoYXQgYXQgZWFjaAppdGVyYXRpb24gY2x1c3RlcnMgYXJlIGNvbm5lY3RlZCBhbmQgd2VsbC1zZXBhcmF0ZWQuIFRoZSBtZXRob2QgaW5jbHVkZXMgYW4KZXh0cmEgc3RlcCBpbiB0aGUgaXRlcmF0aW9uczogYWZ0ZXIgbm9kZXMgYXJlIG1vdmVkIChzdGVwIDEpLCB0aGUgcmVzdWx0aW5nCnBhcnRpdGlvbiBpcyByZWZpbmVkIChzdGVwMikgYW5kIG9ubHkgdGhlbiB0aGUgbmV3IGFnZ3JlZ2F0ZSBuZXR3b3JrIG1hZGUsIGFuZApyZWZpbmVkIChzdGVwIDMpLiBUaGUgZGlhZ3JhbSBiZWxvdyBpcyBjb3BpZWQgZnJvbSBbdGhpcwphcnRpY2xlXShodHRwczovL3d3dy5uYXR1cmUuY29tL2FydGljbGVzL3M0MTU5OC0wMTktNDE2OTUteiNGaWczKS4KCjxpbWcgc3JjPSIuLi9JbWFnZXMvbGVpZGVuX0ZpZzNfSFRNTC5wbmciIHN0eWxlPSJtYXJnaW46YXV0bzsgZGlzcGxheTpibG9jayIgLz4KCgpgYGB7cn0Kc2NlJGxlaWRlbjIwIDwtIGNsdXN0ZXJDZWxscyhzY2UsIAogICAgICAgICAgICAgICAgICAgICAgICAgICB1c2UuZGltcmVkID0gImNvcnJlY3RlZCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBCTFVTUEFSQU0gPSBTTk5HcmFwaFBhcmFtKGsgPSAyMCwgY2x1c3Rlci5mdW4gPSAibGVpZGVuIikpCmBgYAoKCmBgYHtyfQp0YWJsZShzY2UkbGVpZGVuMjApCmBgYAoKVGhlIHQtU05FIHBsb3Qgc2hvd3MgY2VsbHMgY29sb3ItY29kZWQgYnkgY2x1c3RlciBtZW1iZXJzaGlwOgoKYGBge3Igc2hvd19sZWlkZW5fdHNuZV8xLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD04fQpwbG90VFNORShzY2UsIGNvbG91cl9ieT0ibGVpZGVuMjAiLCB0ZXh0X2J5ID0gImxlaWRlbjIwIikKYGBgCgoKIyBBc3Nlc3NpbmcgY2x1c3RlciBiZWhhdmlvdXIKCkEgdmFyaWV0eSBvZiBtZXRyaWNzIGFyZSBhdmFpbGFibGUgdG8gYWlkIHVzIGluIGFzc2Vzc2luZyB0aGUgYmVoYXZpb3VyIG9mIGEgCnBhcnRpY3VsYXIgY2x1c3RlcmluZyBtZXRob2Qgb24gb3VyIGRhdGEuIFRoZXNlIGNhbiBoZWxwIHVzIGluIGFzc2Vzc2luZyBob3cKd2VsbCBkZWZpbmVkIGRpZmZlcmVudCBjbHVzdGVycyB3aXRoaW4gYSBzaW5nbGUgY2x1c3RlcmluZyBhcmUgaW4gdGVybXMgb2YKdGhlIHJlbGF0ZWRuZXNzIG9mIGNlbGxzIHdpdGhpbiB0aGUgY2x1c3RlciBhbmQgdGhlIGhvdyB3ZWxsIHNlcGFyYXRlZCB0aGF0CmNsdXN0ZXIgaXMgZnJvbSBjZWxscyBpbiBvdGhlciBjbHVzdGVycywgYW5kIHRvIGNvbXBhcmUgdGhlIHJlc3VsdHMgb2YgCmRpZmZlcmVudCBjbHVzdGVyaW5nIG1ldGhvZHMgb3IgcGFyYW1ldGVyIHZhbHVlcyAoZS5nLiBkaWZmZXJlbnQgdmFsdWVzCmZvciAqayopLgoKV2Ugd2lsbCBjb25zaWRlciAiU2lsaG91ZXR0ZSB3aWR0aCIgYW5kICJNb2R1bGFyaXR5Ii4gRnVydGhlciBkZXRhaWxzIGFuZCAKb3RoZXIgbWV0cmljcyBhcmUgZGVzY3JpYmVkIGluIHRoZSBbIkFkdmFuY2VkIiBzZWN0aW9uIG9mIHRoZSBPU0NBIGJvb2tdKGh0dHA6Ly9iaW9jb25kdWN0b3Iub3JnL2Jvb2tzL3JlbGVhc2UvT1NDQS5hZHZhbmNlZC9jbHVzdGVyaW5nLXJlZHV4Lmh0bWwjcXVhbnRpZnlpbmctY2x1c3RlcmluZy1iZWhhdmlvcikuCgojIyBTaWxob3VldHRlIHdpZHRoCgpUaGUgc2lsaG91ZXR0ZSB3aWR0aCAoc28gbmFtZWQgYWZ0ZXIgdGhlIGxvb2sgb2YgdGhlIHRyYWRpdGlvbmFsIGdyYXBoIGZvcgpwbG90dGluZyB0aGUgcmVzdWx0cykgaXMgYSBtZWFzdXJlIG9mIGhvdyBjbG9zZWx5IHJlbGF0ZWQgY2VsbHMgd2l0aGluIGNsdXN0ZXIKYXJlIHRvIG9uZSBhbm90aGVyIHZlcnN1cyBob3cgY2xvc2VseSByZWxhdGVkIGNlbGxzIGluIHRoZSBjbHVzdGVyIGFyZSB0byBjZWxscwppbiBvdGhlciBjbHVzdGVycy4gVGhpcyBhbGxvd3MgdXMgdG8gYXNzZXNzIGNsdXN0ZXIgc2VwYXJhdGlvbi4KCkZvciBlYWNoIGNlbGwgaW4gdGhlIGNsdXN0ZXIgd2UgY2FsY3VsYXRlIHRoZSB0aGUgYXZlcmFnZSBkaXN0YW5jZSB0byBhbGwgb3RoZXIKY2VsbHMgaW4gdGhlIGNsdXN0ZXIgYW5kIHRoZSBhdmVyYWdlIGRpc3RhbmNlIHRvIGFsbCBjZWxscyBub3QgaW4gdGhlIGNsdXN0ZXIuClRoZSBjZWxscyBzaWxob3VldHRlIHdpZHRoIGlzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlc2UgZGl2aWRlZCBieSB0aGUKbWF4aW11bSBvZiB0aGUgdHdvIHZhbHVlcy4gQ2VsbHMgd2l0aCBhIGxhcmdlIHNpbGhvdWV0dGUgYXJlIHN0cm9uZ2x5IHJlbGF0ZWQgdG8KY2VsbHMgaW4gdGhlIGNsdXN0ZXIsIGNlbGxzIHdpdGggYSBuZWdhdGl2ZSBzaWxob3VldHRlIHdpZHRoIGFyZSBtb3JlIGNsb3NlbHkKcmVsYXRlZCB0byBvdGhlciBjbHVzdGVycy4KCldlIHdpbGwgdXNlIHRoZSBgYXBwcm94U2lsaG91ZXR0ZWAgZnVuY3Rpb24gZnJvbSB0aGUgKmJsdXN0ZXIqIHBhY2thZ2UuIFRoZSAKcmVzdWx0aW5nIHRhYmxlIGdpdmVzIHVzIHRoZSBzaWxob3VldHRlIHdpZHRoIGZvciBlYWNoIGNlbGwsIHRoZSBjbHVzdGVyIGl0CmJlbG9uZ3MgdG8sIGFuZCB3aGljaCBvdGhlciBjbHVzdGVyIGl0IGlzIG1vc3QgY2xvc2VseSByZWxhdGVkIHRvLgoKYGBge3J9CnNpbC5hcHByb3ggPC0gYXBwcm94U2lsaG91ZXR0ZShyZWR1Y2VkRGltKHNjZSwgImNvcnJlY3RlZCIpLCBjbHVzdGVycz1zY2UkbG91dmFpbjE1KQpzaWwuYXBwcm94CmBgYAoKV2UgY2FuIHZpZXcgdGhlIHJlc3VsdHMgaW4gYXMgYSBiZWVzd2FybSBwbG90LiBXZSBjb2xvdXIgZWFjaCBjZWxsIGFjY29yZGluZyB0bwplaXRoZXIgaXRzIGN1cnJlbnQgY2x1c3RlciBvciwgaWYgdGhlIGNlbGwgaGFzIGEgbmVnYXRpdmUgc2lsaG91ZXR0ZSB3aWR0aCwgdGhlCmNsdXN0ZXIgdGhhdCBpdCBpcyBjbG9zZXN0IHRvLgoKYGBge3J9CnNpbFBsb3QuZGF0IDwtIHNpbC5hcHByb3ggJT4lIAogIGFzLmRhdGEuZnJhbWUoKSAlPiUgCiAgbXV0YXRlKGNsb3Nlc3RDbHVzdGVyID0gaWZlbHNlKHdpZHRoID4gMCwgY2x1c3Rlciwgb3RoZXIpICU+JSBmYWN0b3IoKSkKCmdncGxvdChzaWxQbG90LmRhdCwgYWVzKHg9Y2x1c3RlciwgeT13aWR0aCwgY29sb3VyPWNsb3Nlc3RDbHVzdGVyKSkgKwogIGdnYmVlc3dhcm06Omdlb21fcXVhc2lyYW5kb20obWV0aG9kPSJzbWlsZXkiKQpgYGAKCldlIGNvdWxkIGFsc28gbG9vayBhdCB0aGUgY29ycmVzcG9uZGVuY2UgYmV0d2VlbiBkaWZmZXJlbnQgY2x1c3RlcnMgYnkgcGxvdHRpbmcKdGhlc2UgbnVtYmVycyBvbiBhIGdyaWQgc2hvd2luZyBmb3IgZWFjaCBjbHVzdGVyIG51bWJlciBvZiBjZWxscyBpbiB0aGF0IGNsdXN0ZXIKdGhhdCBhcmUgY2xvc2VyIHRvIGFub3RoZXIgY2x1c3RlciwgY29sb3VyaW5nIGVhY2ggdGlsZSBieSB0aGUgcHJvcG9ydGlvbiBvZiB0aGUKdG90YWwgY2VsbHMgaW4gdGhlIGNsdXN0ZXIgdGhhdCBpdCBjb250YWlucy4gSWRlYWxseSB3ZSB3b3VsZCBsaWtlIHRvIHNlZSBhCnN0cm9uZyBkaWFnb25hbCBiYW5kIGFuZCBvbmx5IGEgZmV3IG9mZi1kaWFnb25hbCB0aWxlcyBjb250YWluaW5nIHNtYWxsIG51bWJlcgpvZiBjZWxscy4KCmBgYHtyfQpwbG90U2lsR3JpZCA8LSBmdW5jdGlvbihkYXQpewogIGRhdCAlPiUgCiAgICBjb3VudChjbHVzdGVyLCBjbG9zZXN0Q2x1c3RlciwgIG5hbWU9Im9sYXAiKSAlPiUgCiAgICBncm91cF9ieShjbHVzdGVyKSAlPiUgCiAgICBtdXRhdGUodG90YWwgID0gc3VtKG9sYXApKSAlPiUgCiAgICBtdXRhdGUocHJvcG9ydGlvbiA9IG9sYXAgLyB0b3RhbCkgJT4lIAogICAgbXV0YXRlKHByb3BvcnRpb24gPSBpZmVsc2UoY2x1c3RlciA9PSBjbG9zZXN0Q2x1c3RlciwgcHJvcG9ydGlvbiwgLXByb3BvcnRpb24pKSAlPiUgCiAgICBnZ3Bsb3QoYWVzKHggPSBjbHVzdGVyLCB5ID0gY2xvc2VzdENsdXN0ZXIpKSArCiAgICAgIGdlb21fdGlsZShhZXMoZmlsbCA9IHByb3BvcnRpb24pKSArCiAgICAgIGdlb21fdGV4dChhZXMobGFiZWwgPSBvbGFwKSwgc2l6ZT01KSArCiAgICAgIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKGNvbG9ycyA9IGMoIiNmYzhkNTkiLCAiI2ZmZmZiZiIsICIjOTFjZjYwIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKC0xLCAxKSkgKwogICAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9c2VxKDAuNSwgMzAuNSwgYnk9MSkpICsKICAgICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0PXNlcSgwLjUsIDMwLjUsIGJ5PTEpLCBjb2xvdXI9ImxpZ2h0Z3JleSIsIGxpbmV0eXBlPTIpICsKICAgICAgZ3VpZGVzKGZpbGwgPSAibm9uZSIpICsKICAgICAgdGhlbWUoCiAgICAgICAgICBhc3BlY3QucmF0aW8gPSAxLAogICAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkKfQpwbG90U2lsR3JpZChzaWxQbG90LmRhdCkKYGBgCkZyb20gdGhlc2UgdHdvIHBsb3RzIHdlIGNhbiBzZWUgdGhhdCBjbHVzdGVycyAyLCA1IGFuZCA4IGFwcGVhciB0byBoYXZlIGEgZ29vZApkZWdyZWUgb2Ygc2VwYXJhdGlvbi4gSW4gY29udHJhc3QsIGNsdXN0ZXJzIDEsIDIgYW5kIDQgaGF2ZSBhIGxhcmdlIG51bWJlciBvZgpjZWxscyB0aGF0IGFwcGVhciB0byBiZSBjbG9zZXIgdG8gY2x1c3RlciAzLiBJdCBpcyBwb3NzaWJsZSBjbHVzdGVycyAxLTQgY29udGFpbgphIGRlZ3JlZSBvZiBoZXRlcm9nZW5laXR5IHRoYXQgc3VnZ2VzdHMgZnVydGhlciBzcGxpdHRpbmcgb2YgdGhlc2UgY2x1c3RlcnMgbWF5CmJlIHJlcXVpcmVkIC0gcGVyaGFwcyB0aGVyZSBzaG91bGQgbW9yZSB0aGFuIDkgY2x1c3RlcnMgdG8gYWRlcXVhdGVseSByZWZsZWN0CnRoZSBiaW9sb2d5LgoKTGV0J3MgZG8gdGhlIHNhbWUgcGxvdHMgd2l0aCB0aGUgd2Fsa3RyYXAgY2x1c3RlcnMgZ2VuZXJhdGVkIHdpdGggKmsqPTE1LgoKYGBge3J9CnNpbC5hcHByb3ggPC0gYXBwcm94U2lsaG91ZXR0ZShyZWR1Y2VkRGltKHNjZSwgImNvcnJlY3RlZCIpLCBjbHVzdGVycz1zY2Ukd2Fsa3RyYXAxNSkKc2lsUGxvdC5kYXQgPC0gc2lsLmFwcHJveCAlPiUgCiAgYXMuZGF0YS5mcmFtZSgpICU+JSAKICBtdXRhdGUoY2xvc2VzdENsdXN0ZXIgPSBpZmVsc2Uod2lkdGggPiAwLCBjbHVzdGVyLCBvdGhlcikgJT4lIGZhY3RvcigpKQoKZ2dwbG90KHNpbFBsb3QuZGF0LCBhZXMoeD1jbHVzdGVyLCB5PXdpZHRoLCBjb2xvdXI9Y2xvc2VzdENsdXN0ZXIpKSArCiAgZ2diZWVzd2FybTo6Z2VvbV9xdWFzaXJhbmRvbShtZXRob2Q9InNtaWxleSIpCgpwbG90U2lsR3JpZChzaWxQbG90LmRhdCkKYGBgCgpUaGlzIGNsdXN0ZXJpbmcgYXBwZWFycyB0byBoYXZlIGdlbmVyYXRlZCBhIHNldCBvZiBjbHVzdGVycyB3aXRoIGJldHRlcgpzZXBhcmF0ZWRuZXNzIHRoYW4gdGhlIExvdXZhaW4gbWV0aG9kIHdpdGggYSAqayogb2YgMTUuCgpBbmQgYWdhaW4gd2l0aCB0aGUgbGVpZGVuIGNsdXN0ZXJzCgpgYGB7cn0Kc2lsLmFwcHJveCA8LSBhcHByb3hTaWxob3VldHRlKHJlZHVjZWREaW0oc2NlLCAiY29ycmVjdGVkIiksIGNsdXN0ZXJzPXNjZSRsZWlkZW4yMCkKc2lsUGxvdC5kYXQgPC0gc2lsLmFwcHJveCAlPiUgCiAgYXMuZGF0YS5mcmFtZSgpICU+JSAKICBtdXRhdGUoY2xvc2VzdENsdXN0ZXIgPSBpZmVsc2Uod2lkdGggPiAwLCBjbHVzdGVyLCBvdGhlcikgJT4lIGZhY3RvcigpKQoKZ2dwbG90KHNpbFBsb3QuZGF0LCBhZXMoeD1jbHVzdGVyLCB5PXdpZHRoLCBjb2xvdXI9Y2xvc2VzdENsdXN0ZXIpKSArCiAgZ2diZWVzd2FybTo6Z2VvbV9xdWFzaXJhbmRvbShtZXRob2Q9InNtaWxleSIpCgpwbG90U2lsR3JpZChzaWxQbG90LmRhdCkKYGBgClRoaXMgc2VlbXMgc2ltaWxhciB0byB0aGUgd2Fsa3RyYXAgY2x1c3RlcmluZy4KCiMjICBNb2R1bGFyaXR5IHRvIGFzc2VzcyBjbHVzdGVycyBxdWFsaXR5CgpBcyBtZW50aW9uZWQgZWFybHksIHRoZSBtb2R1bGFyaXR5IG1ldHJpYyBpcyB1c2VkIGluIGV2YWx1YXRpbmcgdGhlCnNlcGFyYXRlZG5lc3MgYmV0d2VlbiBjbHVzdGVycy4gU29tZSBvZiB0aGUgY2x1c3RlcmluZyBhbGdvcml0aG1zLCBlLmcuIExvdXZhaW4sCnNlZWsgdG8gb3B0aW1pc2UgdGhpcyBmb3IgdGhlIGVudGlyZSBOTiBncmFwaCBhcyBwYXJ0IG9mIHRoZWlyIGNsdXN0ZXIKZGV0ZWN0aW9uLiBNb2R1bGFyaXR5IGlzIGEgcmF0aW8gYmV0d2VlbiB0aGUgb2JzZXJ2ZWQgd2VpZ2h0cyBvZiB0aGUgZWRnZXMKd2l0aGluIGEgY2x1c3RlciB2ZXJzdXMgdGhlIGV4cGVjdGVkIHdlaWdodHMgaWYgdGhlIGVkZ2VzIHdlcmUgcmFuZG9tbHkKZGlzdHJpYnV0ZWQgYmV0d2VlbiBhbGwgbm9kZXMuIFJhdGhlciB0aGFuIGNhbGN1bGF0aW5nIGEgc2luZ2xlIG1vZHVsYXJpdHkgdmFsdWUKZm9yIHRoZSB3aG9sZSBncmFwaCwgd2UgY2FuIGluc3RlYWQgY2FsY3VsYXRlIGEgcGFpci13aXNlIG1vZHVsYXJpdHkgdmFsdWUKYmV0d2VlbiBlYWNoIHBhaXIgb2YgY2x1c3RlcnMgdXNpbmcgdGhlIGBwYWlyd2lzZU1vZHVsYXJpdHlgIGZ1bmN0aW9uIGZyb20gdGhlCipibHVzdGVyKiBwYWNrYWdlLiBGb3IgdGhpcyB3ZSBuZWVkIHRvIGhhdmUgdGhlIGdyYXBoIGZyb20gdGhlIGNsdXN0ZXJpbmcsIHNvIHdlCndpbGwgcmVydW4gdGhlIHdhbGt0cmFwIGNsdXN0ZXJpbmcgd2l0aCBrPTE1IHRvIG9idGFpbiB0aGlzLiBXZSBjYW4gcGxvdCB0aGUKcmVzdWx0aW5nIHJhdGlvcyBvbiBhIGhlYXRtYXAuIFdlIHdvdWxkIGV4cGVjdCB0aGUgaGlnaGVzdCBtb2R1bGFyaXR5IHZhbHVlcwp0byBiZSBvbiB0aGUgZGlhZ29uYWwuCgpgYGB7ciwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9Nn0Kd2Fsa3RyYXAxNSA8LSBjbHVzdGVyQ2VsbHMoc2NlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgdXNlLmRpbXJlZCA9ICJjb3JyZWN0ZWQiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgQkxVU1BBUkFNID0gU05OR3JhcGhQYXJhbShrID0gMTUsIGNsdXN0ZXIuZnVuID0gIndhbGt0cmFwIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bGwgPSBUUlVFKQpnIDwtIHdhbGt0cmFwMTUkb2JqZWN0cyRncmFwaApyYXRpbyA8LSBwYWlyd2lzZU1vZHVsYXJpdHkoZywgd2Fsa3RyYXAxNSRjbHVzdGVycywgYXMucmF0aW89VFJVRSkKcGhlYXRtYXAobG9nMihyYXRpbysxKSwgY2x1c3Rlcl9yb3dzPUZBTFNFLCBjbHVzdGVyX2NvbHM9RkFMU0UsCiAgICBjb2xvcj1jb2xvclJhbXBQYWxldHRlKGMoIndoaXRlIiwgImJsdWUiKSkoMTAwKSkKYGBgCgpMYXJnZWx5LCB0aGlzIHJlZmxlY3RzIHdoYXQgd2Ugc2F3IGZyb20gdGhlIHNpbGhvdWV0dGUgd2lkdGhzLCBidXQgYWxzbyByZXZlYWxzCnNvbWUgYWRkaXRpb25hbCBpbnRlci1jb25uZWN0ZWRuZXNzIGJldHdlZW4gb3RoZXIgY2x1c3RlcnMgZS5nLiBjbHVzdGVyIDEyIGFuZApjbHVzdGVyIDguIFdlIGNhbiBhbHNvIHZpc3VhbGlzZSB0aGlzIGFzIG5ldHdvcmsgZ3JhcGggd2hlcmUgbm9kZXMgYXJlIGNsdXN0ZXJzCmFuZCB0aGUgZWRnZSB3ZWlnaHRzIGFyZSB0aGUgbW9kdWxhcml0eS4KCmBgYHtyfQpjbHVzdGVyLmdyIDwtIGlncmFwaDo6Z3JhcGhfZnJvbV9hZGphY2VuY3lfbWF0cml4KGxvZzIocmF0aW8rMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZT0idXBwZXIiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3ZWlnaHRlZD1UUlVFLCBkaWFnPUZBTFNFKQoKc2V0LnNlZWQoMTEwMDEwMTApCnBsb3QoY2x1c3Rlci5nciwgCiAgICAgZWRnZS53aWR0aD1pZ3JhcGg6OkUoY2x1c3Rlci5ncikkd2VpZ2h0KjUsCiAgICAgbGF5b3V0PWlncmFwaDo6bGF5b3V0X3dpdGhfbGdsKQpgYGAKCiMgQ29tcGFyaW5nIHR3byBzZXRzIG9mIGNsdXN0ZXJzCgpUaGUgd2lkZSB2YXJpZXR5IG9mIGNsdXN0ZXJpbmcgYWxnb3JpdGhtcyBhdmFpbGFibGUgYW5kIHRoZWlyIGRpZmZlcmluZyBvdXRwdXRzCnJlZmxlY3QgdGhlIGRpZmZlcmVudCB3YXlzIHRoYXQgaXQgaXMgcG9zc2libGUgdG8gdmlldyBvdXQgZGF0YSBpbgpoaWdoLWRpbWVuc2lvbmFsIHNwYWNlLiBJdCBtYXkgb2Z0ZW4gYmUgdXNlZnVsIHRvIGFzc2VzcyB0aGUgY29uY29yZGFuY2UgYmV0d2VlbgpkaWZmZXJlbnQgY2x1c3RlcmluZyBtZXRob2RzIGluIG9yZGVyIHRvIGFzc2VzcyBob3cgY2x1c3RlcnMgcmVsYXRlIHRvIGVhY2gKb3RoZXIgLSBlLmcuIGRvZXMgb25lIGNsdXN0ZXIgZnJvbSBvbmUgbWV0aG9kIGVxdWF0ZSB0byBqdXN0IG9uZSBjbHVzdGVyIGluIHRoZQpvdGhlciBvciBpcyBpdCBhIGNvbWJpbmF0aW9uIG9mIGRpZmZlcmVudCBjbHVzdGVycy4gVGhpcyBtYXkgYmUgcmV2ZWFsaW5nIGFib3V0CnRoZSB1bmRlcmx5aW5nIGJpb2xvZ3kuIFdlIHdpbGwgdXNlIHRoZSBKYWNjYXJkIGluZGV4IGFzIG1lYXN1cmUgb2YgY29uY29yZGFuY2UKYmV0d2VlbiBjbHVzdGVycy4gQSB2YWx1ZSBvZiAxIHJlcHJlc2VudHMgcGVyZmVjdCBjb25jb3JkYW5jZSBiZXR3ZWVuIGNsdXN0ZXJzCihpLmUuIHRoZXkgY29udGFpbiBleGFjdGx5IHRoZSBzYW1lIGNlbGxzKS4KCmBgYHtyIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTR9CmphY2MubWF0IDwtIGxpbmtDbHVzdGVyc01hdHJpeChzY2UkbG91dmFpbjE1LCBzY2Ukd2Fsa3RyYXAxNSkKcm93bmFtZXMoamFjYy5tYXQpIDwtIHBhc3RlKCJMb3V2YWluIiwgcm93bmFtZXMoamFjYy5tYXQpKQpjb2xuYW1lcyhqYWNjLm1hdCkgPC0gcGFzdGUoIldhbGt0cmFwIiwgY29sbmFtZXMoamFjYy5tYXQpKQpwaGVhdG1hcChqYWNjLm1hdCwgY29sb3I9dmlyaWRpczo6dmlyaWRpcygxMDApLCBjbHVzdGVyX2NvbHM9RkFMU0UsIGNsdXN0ZXJfcm93cz1GQUxTRSkKYGBgCgpXZSBjYW4gc2VlIHRoYXQgTG91dmFpbiBjbHVzdGVycyAxLCA1LCA2IGFuZCA3IGFyZSBlcXVpdmFsZW50IHRvIHdhbGt0cmFwCmNsdXN0ZXJzIDIsIDEzLCA2LCBhbmQgMTEuIFRoZSByZW1haW5pbmcgTG91dmFpbiBjbHVzdGVycyBhcmUgY29tYmluYXRpb25zIG9mCmNlbGxzIGZyb20gdmFyaW91cyB3YWxrdHJhcCBjbHVzdGVycy4gV2UgbWF5IHdhbnQgdG8gbG9vayBhdCBtYXJrZXIgZ2VuZXMgZm9yCnRoZXNlIGNsdXN0ZXJzIHRvIGFzc2VzcyB3aGF0IHRoZXNlIHR3byBkaWZmZXJlbnQgdmlld3MgYXJlIHRlbGxpbmcgdXMgYWJvdXQgdGhlCmJpb2xvZ3kuCgojIENsdXN0ZXIgc3dlZXAKCkFzIHdlIGhhdmUgc2VlbiwgdGhlcmUgYXJlIGEgbnVtYmVyIG9mIGRpZmZlcmVudCBwYXJhbWV0ZXJzIHdlIGNhbiBjaGFuZ2UgdG8KYWx0ZXIgdGhlIGZpbmFsIGNsdXN0ZXJpbmcgcmVzdWx0IC0gcHJpbWFyaWx5IHRoZSAqayogdXNlZCB0byBidWlsZCB0aGUgTk4KZ3JhcGgsIHRoZSBlZGdlIHdlaWdodGluZyBtZXRob2QgYW5kIHRoZSBjbHVzdGVyaW5nIGFsZ29yaXRobS4gVGhlcmUgaXMgbm8gb25lCmdvbGQgc3RhbmRhcmQgdGhhdCB3aWxsIGZpdCBhbGwgZGF0YSwgc28sIGluIG1vc3QgY2FzZXMsIGl0IGlzIG5lY2Vzc2FyeSB0bwphc3Nlc3MgYSBudW1iZXIgb2YgZGlmZmVyZW50IGNsdXN0ZXJpbmdzIHRvIG9idGFpbiBvbmUgdGhhdCBwcm92aWRlcyBhIHZpZXcgb2YKdGhlIGRhdGEgdGhhdCBzdWl0cyBvdXIgYmlvbG9naWNhbCBpbnRlcnByZXRhdGlvbnMuIFRoZSBgY2x1c3RlclN3ZWVwYCBmdW5jdGlvbgphbGxvd3MgdXMgdG8gYXBwbHkgYSByYW5nZSBvZiBkaWZmZXJlbnQgcGFyYW1ldGVycyBpbiBvbmUgZ28gYW5kIG9idGFpbiB0aGUKY2x1c3RlcmluZyBmb3IgZWFjaC4KCkZvciBleGFtcGxlLCBzdXBwb3NlIHdlIHdpc2ggdG8gYXNzZXNzIHRoZSBlZmZlY3Qgb2YgZGlmZmVyZW50IHZhbHVlcyBvZiAqayogb24KdGhlIHdhbGt0cmFwIGNsdXN0ZXJpbmcuIFdlIGNhbiBwYXJhbGxlbGl6ZSB0aGlzIHByb2Nlc3MgdG8gbWFrZSBpdCBmYXN0ZXIuCgpgYGB7cn0Kb3V0IDwtIGNsdXN0ZXJTd2VlcChyZWR1Y2VkRGltKHNjZSwgImNvcnJlY3RlZCIpLAogICAgICAgICAgICAgICAgICAgIEJMVVNQQVJBTSA9IE5OR3JhcGhQYXJhbSgpLAogICAgICAgICAgICAgICAgICAgIGsgPSBhcy5pbnRlZ2VyKGMoNSwgMTAsIDE1LCAyMCwgMjUpKSwKICAgICAgICAgICAgICAgICAgICBjbHVzdGVyLmZ1biA9ICJ3YWxrdHJhcCIsCiAgICAgICAgICAgICAgICAgICAgQlBQQVJBTT1CaW9jUGFyYWxsZWw6Ok11bHRpY29yZVBhcmFtKDUpKQpgYGAKClRoZSByZXN1bHRpbmcgb2JqZWN0IGlzIGxpc3QgY29udGFpbmluZyBhIERhdGFGcmFtZSB3aXRoIHRoZSBjbHVzdGVycyBmb3IgZWFjaApjb21iaW5hdGlvbiBvZiB0aGUgY2x1c3RlcmluZyBwYXJhbWV0ZXJzIGFuZCBhIGNvcnJlc3BvbmRpbmcgRGF0YUZyYW1lIHNob3dpbmcKdGhlIHBhcmFtZXRlcnMgdXNlZCB0byBnZW5lcmF0ZSBlYWNoIG9mIHRoZXNlOgoKYGBge3J9Cm91dCRjbHVzdGVyc1ssMTo0XQpvdXQkcGFyYW1ldGVycwpgYGAKCldlIGNhbiB0aGVuIGNvbWJpbmUgdGhpcyBjbHVzdGVyIHN3ZWVwIHdpdGggdGhlIG1ldHJpY3MgZm9yIGFzc2Vzc2luZyBjbHVzdGVyCmJlaGF2aW91ciBpbiBvcmRlciB0byBnZXQgYSBvdmVydmlldyBvZiB0aGUgZWZmZWN0cyBvZiB0aGVzZSBwYXJhbWV0ZXIgY2hhbmdlcwp0aGF0IG1heSBlbmFibGUgdXMgdG8gbWFrZSBzb21lIGRlY2lzaW9ucyBhcyB0byB3aGljaCBjbHVzdGVyaW5nIG9yIGNsdXN0ZXJpbmdzCndlIG1heSB3aXNoIHRvIGludmVzdGlnYXRlIGZ1cnRoZXIuCgpIZXJlIHdlIHdpbGwganVzdCBsb29rIGF0IHRoZSBtZWFuIHNpbGhvdWV0dGUgd2lkdGggYW5kIHRoZSBudW1iZXIgb2YgY2x1c3RlcnMuCgpgYGB7cn0KZGYgPC0gYXMuZGF0YS5mcmFtZShvdXQkcGFyYW1ldGVycykKCiMgZ2V0IHRoZSBudW1iZXIgb2YgY2x1c3RlcnMKZGYkbnVtLmNsdXN0ZXJzIDwtIGFwcGx5KG91dCRjbHVzdGVycywgMiwgbWF4KQoKIyBnZXQgdGhlIG1lYW4gc2lsaG91ZXR0ZSB3aWR0aApnZXRNZWFuU2lsIDwtIGZ1bmN0aW9uKGNsdXN0ZXIpIHsKICAgIHNpbCA8LSBhcHByb3hTaWxob3VldHRlKHJlZHVjZWREaW0oc2NlLCAiY29ycmVjdGVkIiksIGNsdXN0ZXIpCiAgICBtZWFuKHNpbCR3aWR0aCkKfQpkZiRzaWxob3VldHRlIDwtIG1hcF9kYmwoYXMubGlzdChvdXQkY2x1c3RlcnMpLCBnZXRNZWFuU2lsKQoKbmNsUGxvdCA8LSBnZ3Bsb3QoZGYsIGFlcyh4ID0gaywgeSA9IG51bS5jbHVzdGVycykpICsgCiAgICAgICAgICAgICAgICAgIGdlb21fbGluZShsd2Q9MikKc2lsUGxvdCA8LSBnZ3Bsb3QoZGYsIGFlcyh4ID0gaywgeSA9IHNpbGhvdWV0dGUpKSArIAogICAgICAgICAgICAgICAgICBnZW9tX2xpbmUobHdkPTIpCm5jbFBsb3QgKyBzaWxQbG90CmBgYAoKQmFzZWQgb24gb3VyIHByZXZpb3VzIGFuYWx5c2lzIGFuZCBrbm93bGVkZ2Ugb2YgdGhlIGJpb2xvZ3kgd2UgbWF5IGZlZWwgdGhhdCAxMwpjbHVzdGVycyByZXByZXNlbnRzIGEgZ29vZCBudW1iZXIgY2x1c3RlcnMsIGJ1dCB3ZSBjYW4gc2VlIGhlcmUgdGhhdCAqayogPSAxNSwKKmsqID0gMjAgYW5kICprKiA9IDI1IHByb3ZpZGUgdGhpcywgYnV0ICprKiA9IDI1IGdpdmVzIHVzIGEgYmV0dGVyIHNpbGhvdWV0dGUKc2NvcmUuIE9uIHRoZSBvdGhlciBoYWQsIHBlcmhhcHMgKmsqID0gMTAgcHJvdmlkZXMgZ3JlYXRlciByZXNvbHV0aW9uIG9mIGNlbGwKdHlwZXMsIHdpdGggbW9yZSBjbHVzdGVycyB3aXRoIG9ubHkgYSBzbGlnaHQgZGVjcmVhc2UgaW4gdGhlIHNpbGhvdWV0dGUgc2NvcmUuCgpFYXJsaWVyIHdlIGxvb2tlZCBhdCB0aGUgSmFjY2FyZCBpbmRleCBhcyBhIG1lYW5zIG9mIGNvbXBhcmluZyB0d28gZGlmZmVyZW50CmNsdXN0ZXJpbmdzLiBXZSBjb3VsZCBhcHBseSB0aGUgc2FtZSBtZXRob2QgaGVyZToKCmBgYHtyIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTV9CmphY2MubWF0IDwtIGxpbmtDbHVzdGVyc01hdHJpeChvdXQkY2x1c3RlcnMkay4xMF9jbHVzdGVyLmZ1bi53YWxrdHJhcCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdXQkY2x1c3RlcnMkay4xNV9jbHVzdGVyLmZ1bi53YWxrdHJhcCkKcm93bmFtZXMoamFjYy5tYXQpIDwtIHBhc3RlKCJXYWxrdHJhcF8xMCIsIHJvd25hbWVzKGphY2MubWF0KSkKY29sbmFtZXMoamFjYy5tYXQpIDwtIHBhc3RlKCJXYWxrdHJhcF8xNSIsIGNvbG5hbWVzKGphY2MubWF0KSkKcGhlYXRtYXAoamFjYy5tYXQsIGNvbG9yPXZpcmlkaXM6OnZpcmlkaXMoMTAwKSwgY2x1c3Rlcl9jb2xzPUZBTFNFLCBjbHVzdGVyX3Jvd3M9RkFMU0UpCmBgYAoKRmluYWxseSwgd2UgY2FuIGFkZCBhbGwgKG9yIGEgc3Vic2V0KSBvZiB0aGUgY2x1c3RlcmluZ3MgZnJvbSBgY2x1c3RlclN3ZWVwYCB0bwpvdXIgU0NFIG9iamVjdC4KCmBgYHtyfQpjb2xEYXRhKHNjZSkgPC0gY2JpbmQoY29sRGF0YShzY2UpLCBEYXRhRnJhbWUob3V0JGNsdXN0ZXJzKSkKYGBgCgoKVGhlIFtPU0NBCmJvb2tdKGh0dHA6Ly9iaW9jb25kdWN0b3Iub3JnL2Jvb2tzL3JlbGVhc2UvT1NDQS5hZHZhbmNlZC9jbHVzdGVyaW5nLXJlZHV4Lmh0bWwjY29tcGFyaW5nLWRpZmZlcmVudC1jbHVzdGVyaW5ncykKcHJvdmlkZXMgc29tZSBhZGRpdGlvbmFsIG1ldGhvZHMgZm9yIGNvbXBhcmluZyBkaWZmZXJlbnQgY2x1c3RlcmluZ3MgdGhhdCBjYW4gYmUKY29tYmluZWQgd2l0aCB0aGUgY2x1c3RlciBzd2VlcCByZXN1bHRzIHRvIGFzc2VzcyBjbHVzdGVyIGJlaGF2aW91ciB1bmRlcgpkaWZmZXJlbnQgcGFyYW1ldGVycy4KCkluIHRoaXMgc2VjdGlvbiwgd2UgaGF2ZSBqdXN0IGRvbmUgYSBzd2VlcCBjaGFuZ2luZyB0aGUgKmsqLCBidXQgaXQgaXMgYWxzbwpwb3NzaWJsZSB0byBjb21iaW5lIHRoaXMgd2l0aCBtdWx0aXBsZSBjbHVzdGVyaW5nIGFsZ29yaXRobXMgYW5kIG11bHRpcGxlIGVkZ2UKd2VpZ2h0aW5ncy4KCioqTm90ZToqKiBJbiBwcmFjdGljZSwgb24gYSBmdWxsIGRhdGEgc2V0LCB3aXRoIG11bHRpcGxlIGFsZ29yaXRobXMgYW5kIHZhbHVlcwpvZiAqayosIHRoaXMgY2FuIHRha2UgYSBsb25nIHRpbWUgdG8gcnVuLiBVc3VhbGx5IEkgZG8gbm90IHJ1biB0aGlzCmludGVyYWN0aXZlbHkgaW4gUiBzdHVkaW8sIGJ1dCBpbnN0ZWFkIHdyaXRlIGFuIFIgc2NyaXB0IHRoYXQgd2lsbCBlbmQgYnkKZXhwb3J0aW5nIHRoZSBmaW5hbCBvdXRwdXQgb2YgYGNsdXN0ZXJTd2VlcGAgdG8gYW4gUkRTIG9iamVjdCB3aGljaCBJIGNhbiBsYXRlcgpsb2FkIGludG8gUi4gVGhpcyBSIHNjcmlwdCBjYW4gdGhlbiBiZSBzZW50IGFzIGEgam9iIHRvIHRoZSBjbHVzdGVyLiBUaGlzIGlzCmFsc28gbWVhbnMgdGhlIGpvYiBjYW4gYmUgbWFzc2l2ZWx5IG1vcmUgcGFyYWxsZWxpemVkIGJ5IHVzaW5nIG1vcmUgY29yZXMuCldlIHdpbGwgc2VlIHRoaXMgaW4gdGhlIGV4ZXJjaXNlIGZvciB0aGlzIHNlc3Npb24uCgojIEZpbmFsaXNlIGNsdXN0ZXJpbmcgc2VsZWN0aW9uCgpXaGVuIHlvdSBoYXZlIGNvbWUgdG8gYSBkZWNpc2lvbiBhYm91dCB3aGljaCBjbHVzdGVyaW5nIHRvIHVzZSBpdCBpcyBjb252ZW5pZW50CnRvIGFkZCBpdCB0byBgY29sRGF0YWAgY29sdW1uIGNhbGxlZCAibGFiZWwiIHVzaW5nIHRoZSBgY29sTGFiZWxzYCBmdW5jdGlvbi4KVGhpcyBtZWFucyBkb3duc3RyZWFtIGNvZGUgZG9lcyBub3QgbmVlZCB0byBiZSBjaGFuZ2VkIHNob3VsZCB5b3UgbGF0ZXIgZGVjaWRlCnRvIHN3aXRjaCB0byBhIGRpZmZlcmVudCBjbHVzdGVyaW5nIChhbmQgbWFrZXMgdGhlIGNvZGUgZWFzaWx5IHJlLXVzYWJsZSBmb3IgCmRpZmZlcmVudCBhbmFseXNlcykuCgpGb3Igbm93IHdlIHdpbGwgdXNlIHRoZSB3YWxrdHJhcCBrPTI1IGNsdXN0ZXJpbmcuCgpgYGB7cn0KY29sTGFiZWxzKHNjZSkgPC0gc2NlJGsuMjVfY2x1c3Rlci5mdW4ud2Fsa3RyYXAKYGBgCgoKIyBFeHByZXNzaW9uIG9mIGtub3duIG1hcmtlciBnZW5lcwoKSWYgd2UgZXhwZWN0IG91ciBjbHVzdGVycyB0byByZXByZXNlbnQga25vd24gY2VsbCB0eXBlcyBmb3Igd2hpY2ggdGhlcmUgYXJlIAp3ZWxsIGVzdGFibGlzaGVkIG1hcmtlciBnZW5lcywgd2UgY2FuIG5vdyBzdGFydCB0byBpbnZlc3RpZ2F0ZSB0aGUgY2x1c3RlcnMKYnkgcGxvdHRpbmcgaW4gcGFyYWxsZWwgdGhlIGV4cHJlc3Npb24gb2YgdGhlc2UgZ2VuZXMuIFRoaXMgY2FuIGFsc28gaGVscCB1cwppbiBhc3Nlc3NpbmcgaWYgb3VyIGNsdXN0ZXJpbmcgaGFzIHNhdGlzZmFjdG9yaWx5IHBhcnRpdGlvbmVkIG91ciBjZWxscy4KCmBgYHtyLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD04fQpwbG90VFNORShzY2UsIGNvbG91cl9ieSA9ICJsYWJlbCIsIHRleHRfYnkgPSAibGFiZWwiKSArCiAgZ2d0aXRsZSgiV2Fsa3RyYXAgY2x1c3RlcnMiKQpgYGAKCkhhdmluZyBpZGVudGlmaWVkIGNsdXN0ZXJzLCB3ZSBub3cgZGlzcGxheSB0aGUgbGV2ZWwgb2YgZXhwcmVzc2lvbiBvZiBjZWxsIHR5cGUKbWFya2VyIGdlbmVzIHRvIHF1aWNrbHkgbWF0Y2ggY2x1c3RlcnMgd2l0aCBjZWxsIHR5cGVzLiBGb3IgZWFjaCBtYXJrZXIgd2Ugd2lsbApwbG90IGl0cyBleHByZXNzaW9uIG9uIGEgdC1TTkUsIGFuZCBzaG93IGRpc3RyaWJ1dGlvbiBhY3Jvc3MgZWFjaCBjbHVzdGVyIG9uIGEKdmlvbGluIHBsb3QuCgpXZSB3aWxsIGJlIHVzaW5nIGdlbmUgc3ltYm9scyB0byBpZGVudGlmeSB0aGUgbWFya2VyIGdlbmVzLCBzbyB3ZSB3aWxsIHN3aXRjaAp0aGUgcm93bmFtZXMgaW4gdGhlIFNDRSBvYmplY3QgdG8gYmUgZ2VuZSBzeW1ib2xzLiBXZSB1c2UgdGhlIHNjYXRlciBmdW5jdGlvbgpgdW5pcXVpZnlGZWF0dXJlTmFtZXNgIHRvIGRvIHRoaXMgYXMgdGhlcmUgYXJlIGEgZmV3IGR1cGxpY2F0ZWQgZ2VuZSBzeW1ib2xzLgoKYGBge3J9CnJvd25hbWVzKHNjZSkgPC0gdW5pcXVpZnlGZWF0dXJlTmFtZXMocm93RGF0YShzY2UpJElELCByb3dEYXRhKHNjZSkkU3ltYm9sKQpgYGAKCiMjIEItY2VsbHMgbWFya2VycwoKYGBge3IgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTJ9CnAxIDwtIHBsb3RUU05FKHNjZSwgYnlfZXhwcnNfdmFsdWVzID0gInJlY29uc3RydWN0ZWQiLAogICAgICAgICAgICAgICBjb2xvdXJfYnkgPSAiTVM0QTEiLAogICAgICAgICAgICAgICB0ZXh0X2J5ID0gImxhYmVsIikKcDIgPC0gcGxvdFRTTkUoc2NlLCBieV9leHByc192YWx1ZXMgPSAicmVjb25zdHJ1Y3RlZCIKICAgICAgICAgICAgICAgLCBjb2xvdXJfYnkgPSAiQ0Q3OUEiLAogICAgICAgICAgICAgICB0ZXh0X2J5ID0gImxhYmVsIikKcDEgKyBwMgpgYGAKCmBgYHtyIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTEyfQpwbG90RXhwcmVzc2lvbihzY2UsIAogICAgICAgICAgICAgICBleHByc192YWx1ZXMgPSAicmVjb25zdHJ1Y3RlZCIsCiAgICAgICAgICAgICAgIHggPSAibGFiZWwiLCAKICAgICAgICAgICAgICAgY29sb3VyX2J5ID0gImxhYmVsIiwKICAgICAgICAgICAgICAgZmVhdHVyZXM9YygiTVM0QTEiLCAiQ0Q3OUEiKSkKYGBgCgoKIyMgTW9ub2N5dGUgbWFya2VycwoKYGBge3IgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTJ9CnAxIDwtIHBsb3RUU05FKHNjZSwgYnlfZXhwcnNfdmFsdWVzID0gInJlY29uc3RydWN0ZWQiLAogICAgICAgICAgICAgICBjb2xvdXJfYnkgPSAiRkNHUjNBIiwKICAgICAgICAgICAgICAgdGV4dF9ieSA9ICJsYWJlbCIpCnAyIDwtIHBsb3RUU05FKHNjZSwgYnlfZXhwcnNfdmFsdWVzID0gInJlY29uc3RydWN0ZWQiLCAKICAgICAgICAgICAgICAgY29sb3VyX2J5ID0gIk1TNEE3IiwKICAgICAgICAgICAgICAgdGV4dF9ieSA9ICJsYWJlbCIpCnAxICsgcDIKYGBgCgpgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMn0KcGxvdEV4cHJlc3Npb24oc2NlLCAKICAgICAgICAgICAgICAgZXhwcnNfdmFsdWVzID0gInJlY29uc3RydWN0ZWQiLAogICAgICAgICAgICAgICB4ID0gImxhYmVsIiwgCiAgICAgICAgICAgICAgIGNvbG91cl9ieSA9ICJsYWJlbCIsCiAgICAgICAgICAgICAgIGZlYXR1cmVzPWMoIkZDR1IzQSIsICJNUzRBNyIpKQpgYGAKCiMjIERlbmRyaXRpYyBjZWxsIG1hcmtlcnMKCmBgYHtyIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTEyfQpwMSA8LSBwbG90VFNORShzY2UsIGJ5X2V4cHJzX3ZhbHVlcyA9ICJyZWNvbnN0cnVjdGVkIiwgCiAgICAgICAgICAgICAgIGNvbG91cl9ieSA9ICJGQ0VSMUEiLAogICAgICAgICAgICAgICB0ZXh0X2J5ID0gImxhYmVsIikKcDIgPC0gcGxvdFRTTkUoc2NlLCBieV9leHByc192YWx1ZXMgPSAicmVjb25zdHJ1Y3RlZCIsIAogICAgICAgICAgICAgICBjb2xvdXJfYnkgPSAiQ1NUMyIsCiAgICAgICAgICAgICAgIHRleHRfYnkgPSAibGFiZWwiKQpwMSArIHAyCmBgYAoKYGBge3IgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTJ9CnBsb3RFeHByZXNzaW9uKHNjZSwgCiAgICAgICAgICAgICAgIGV4cHJzX3ZhbHVlcyA9ICJyZWNvbnN0cnVjdGVkIiwKICAgICAgICAgICAgICAgeCA9ICJsYWJlbCIsIAogICAgICAgICAgICAgICBjb2xvdXJfYnkgPSAibGFiZWwiLAogICAgICAgICAgICAgICBmZWF0dXJlcz1jKCJGQ0VSMUEiLCAiQ1NUMyIpKQpgYGAKCgojIyBTYXZlIGRhdGEKCldyaXRlIFNDRSBvYmplY3QgdG8gZmlsZS4KCmBgYHtyfQpzYXZlUkRTKHNjZSwgZmlsZT0iUm9iamVjdHMvQ2Fyb25fY2x1c3RlcmluZ19tYXRlcmlhbC5yZHMiKQpgYGAKCiMjIFNlc3Npb24gaW5mb3JtYXRpb24KCjxkZXRhaWxzPgpgYGB7cn0Kc2Vzc2lvbkluZm8oKQpgYGAKPC9kZXRhaWxzPg==